Skip to content

deps: skillinject v0.2.4, dataexchange v0.2.3, updater v0.2.5 (+ pilotctl update status) - #468

Merged
TeoSlayer merged 4 commits into
mainfrom
deps/wave2-libs
Sep 24, 2026
Merged

TeoSlayer merged 4 commits into
mainfrom
deps/wave2-libs

Conversation

@TeoSlayer

@TeoSlayer TeoSlayer commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Bumps the three wave-2 libraries and makes the web4 changes their PRs ask for: skillinject#39, dataexchange#43 and updater#49.

02fd110 fixes the review findings. It checks the recorded restart_error against the version the daemon reports (F1, F2). The fake updater merges the on-disk record the way the real one does (F3). skills status --verbose works again (F4). The skills summary says what each retired-surface removal did to the file and whether injection is disabled (F5).

Dependency bumps

Module From To
github.com/pilot-protocol/skillinject v0.2.4-beta.4 v0.2.4
github.com/pilot-protocol/dataexchange v0.2.2 v0.2.3
github.com/pilot-protocol/updater v0.2.4 v0.2.5
github.com/pilot-protocol/common v0.5.13 v0.5.14 (pulled in by skillinject and dataexchange)

The other go.mod changes are indirect: sigstore-go v1.3.0, timestamp-authority v2.1.3, go-logr v1.4.4 and the go-openapi set. updater v0.2.5 requires them in its own go.mod, so MVS pulls them in and they cannot be left out. No other direct dependency changed. go stays 1.25.13 and grpc stays v1.83.2.

updater#49 follow-up (cmd/pilotctl/updates.go)

Problem: pilotctl update called u.RunOnce() and then always printed success. pilotctl --json update printed {"status":"ok"} and exited 0 even on a GitHub 403. Manual runs were not recorded anywhere. update status showed only the auto-update flag and pilotctl's version.

Changes:

  • StatusPath. pilotctl update passes StatusPath: ~/.pilot/update-state.json, using configDir(), so PILOT_HOME is honored. The pilot-updater loop already writes this file, because the library defaults it to the directory of --state-path. Manual and automatic checks now share one record. cmd/updater needs no change.
  • RunOnce error. A failed check now exits 1 through fatalHint("update_failed", …). The message is update failed: <updater error> and the hint points to pilotctl update status. With --json, stdout is empty and stderr gets the usual error envelope.
  • Successful runs print Updated to vX. or Already up to date (vX).. The exit code stays 0 because the install succeeded.
  • restart_error is checked against the daemon before pilotctl reports it. The record alone can be out of date. updater v0.2.5 keeps an earlier restart_error after a manual run that restarted the daemon: every release replaces pilot-updater, and recordCheck clears the field only when it did not. It also clears it on a later check only on Linux, from /proc. So when a restart_error is recorded, pilotctl asks the daemon over IPC which version it runs (info.version, bounded to 3 s):
    • Daemon runs the installed version: the record is out of date. No warning is printed.
    • Daemon runs another version: a warning goes to stderr with both versions and the updater's message. The warning names a restart command (pilotctl daemon stop && pilotctl daemon start) when that message has none; the macOS message only names the launchctl kickstart that failed.
    • No daemon answers: a run that installed a release warns that the daemon is not running and how to start it. An up-to-date run says nothing, because nothing runs the old binaries.
    • When an update run leaves restart_error unchanged from before the run, pilotctl waits up to 30 s for a just-restarted daemon to answer (pilotctl daemon start waits 30 s too). A restart_error this run recorded is checked once, without waiting.
  • --json adds result, updated, current_version, latest_version, restart_error, restart_needed, daemon_running, daemon_version and status_file. restart_error is "" when the daemon runs the installed version.
  • pilotctl update status reads the record with updater.ReadStatus:
    • Text: the last check (time, auto or manual, result), the last error, failures in a row and the last success, installed and latest (or pinned) versions, and the last update. With a restart_error recorded it shows one of three lines. Daemon restart: NEEDED — the daemon runs vA, installed is vB means the daemon runs another version. daemon not running — vB runs when it starts comes with the start command. not needed — the daemon runs the installed vB means the record is out of date.
    • --json: adds status_file, last_result, last_error, restart_error, restart_needed, daemon_running, daemon_version and the full record as update_state. update_state keeps the record as written and is null when no check has been recorded. A file that cannot be read or parsed is reported as status_error and does not fail the command.
  • newUpdateRunner and daemonVersionProbe are package vars so tests can use a fake updater and a fake daemon. Production behavior is unchanged.
  • The root cause is in updater: it should clear restart_error after a manual run whose restart succeeded, and after a check that finds the daemon on the installed version on macOS. That needs an updater release, so it is not in this PR. The daemon check here stays correct either way.
  • The update help text and the context "returns" string describe the new output. The top-level --help did not change. I ran scripts/gen-cli-reference.sh and docs/cli-reference.md came out byte-identical, so it is not in the diff.

Not done here (updater#49 lists it under "Later"): using loop_pid and next_check_at to warn that no updater loop is running.

skillinject#39 follow-up (cmd/pilotctl/skills.go)

Problem: skillinject v0.2.4 reports retired surfaces as state=retired, action=remove and adds Outcome.Note, Removal.Note and RemovalNeutralized. Before this PR, the summaries dropped all of them.

Changes:

  • skills check and skills enable now share one summary:
    • remove: N (retired surfaces from an older manifest) and a Retired surfaces cleaned up: list. Both print only when N > 0. Each row says what happened to the file: helpers and plugin files are deleted. From a heartbeat file only the pilot block is stripped, and from openclaw.json only the plugin entry is removed; both lines say file kept.
    • With skill injection disabled, a pass installs nothing and only prunes retired surfaces (new in skillinject v0.2.4). The summary then says Skill injection is disabled: nothing was installed or updated. and Reconcile complete — retired surfaces only, N found., and --json adds disabled.
    • A Notes: block for any row that has a note. That is a heartbeat file shared with another tool, or a retired plugin that was neutralized instead of removed.
    • --json adds removes and notes (always an array). enable --json now also includes noops.
  • The pilotctl update skills line is now (… up-to-date, … installed, N removed, … errors) and is followed by any notes. In disabled mode it reads Skills: injection disabled — retired surfaces cleaned up: N, errors: M.
  • skills disable all counts neutralized and prints note: under each processed path. The count column is 2 characters wider to fit neutralized:.
  • skills status: --json includes each row's note and --verbose prints it. main() takes --verbose and -v as the global flag before dispatch, so skills status --verbose never saw the flag; only --verbose=true worked, and that bug is on main too. skills status now also honors the global flag.

dataexchange#43

No web4 change needed:

  • The inbox byte-cap fix and the dedupe run on the receiving daemon. cmd/daemon builds ServiceConfig with the defaults, which now evict the oldest files instead of the whole inbox.
  • pilotctl never sets MessageID or ReplyTo, so its frames stay untagged and are byte-for-byte unchanged on the wire.
  • Matching --wait replies by request ID is the separate batch-3b PR (see dataexchange#43's deploy notes).

Tests

All run with GOWORK=off on 02fd110 (the review fixes). 1af6e22 passed the same set.

  • go build ./... ok. GOOS=linux go build ./... ok.
  • go vet ./cmd/... ./pkg/daemon/ is clean, and gofmt -l is clean.
  • go test ./cmd/... ./pkg/daemon/... -count=1: all ok. That covers cmd/daemon, cmd/pilotctl, cmd/updater, pkg/daemon and its subpackages.
  • go test ./tests/ -run TestDataExchange -count=1: ok. This is the integration check against dataexchange v0.2.3.
  • New cmd/pilotctl/zz_update_state_test.go (uses a fake updater and a fake daemon probe; no network, no real daemon):
    • The fake merges each check into the update-state.json already on disk, the way updater v0.2.5's recordCheck does. That includes leaving an earlier restart_error in place.
    • StatusPath, InstallDir and pin are passed through.
    • A RunOnce error exits with update_failed in text and JSON, with empty stdout in JSON.
    • A failed kickstart followed by a successful one: no warning once the restarted daemon reports the installed version. pilotctl waits for it (the daemon answers on the 4th probe). JSON shows restart_error: "" and restart_needed: false. If the daemon never reports the new version, restart_needed is true.
    • An up-to-date run on a record that still holds a restart_error, with the daemon already restarted: no warning, and update status says not needed. With the daemon stopped, update status says daemon not running and gives the start command.
    • A restart_error this run recorded is probed once, without waiting.
    • printUpdateResult covers three install cases: the daemon on the old version (with the restart hint for the macOS message, and no second command for a Linux one), the daemon on the new version, and no daemon. It covers three up-to-date cases: no restart error, no daemon, and the daemon on the old version.
    • update status for no record, a failing record with a restart error, a healthy pinned record, and an unreadable file.
    • End to end: a failed pilotctl update is then shown by update status.
  • New cmd/pilotctl/zz_skills_summary_test.go:
    • The counts and notes in the JSON fields, with notes: [] when empty.
    • The text summary with and without removes, and what each removal did per row kind (marker, allow-list, plugin file, helper).
    • The disabled-mode summary, JSON disabled, and the disabled update line.
    • The update summary line.
    • disable all with a neutralized row.
    • skills status detail follows the global --verbose/-v that main() consumes (driven through main()), and --verbose=true.
  • Red checks:
    • With the RunOnce error ignored again and StatusPath unset, 3 of the new update tests fail.
    • Trusting the recorded restart_error (the 1af6e22 behavior) fails 5 update tests.
    • Dropping the wait for a restarted daemon fails the F1 test.
    • Reverting the four skills changes fails 4 skills tests.
  • End to end with the real v1.13.9 release (SLSA-verified download) in a scratch HOME and install dir. A fake launchctl restarts a fake IPC daemon that reports the version in .pilot-version. Nothing touched ~/.pilot or the running daemon.
    • The 1af6e22 binary reproduces both review findings: the stale warning right after a successful restart, and the warning plus NEEDED on the next up-to-date run.
    • The 02fd110 binary, when the kickstart fails and the daemon is still on v1.13.8, warns (it runs v1.13.8, installed is v1.13.9) with the restart command.
    • After a successful kickstart it prints no warning, and --json shows restart_error: "" and daemon_version: v1.13.9. The run took 8 s including the download.
    • The next up-to-date run prints no warning, and update status says not needed.
    • With the daemon stopped, an up-to-date run is silent and update status says daemon not running … start it with: pilotctl daemon start. An update run with the daemon stopped warns that it is not running.
  • skills with the real binary and live manifest, in a scratch HOME set up like the review repro:
    • skills status --verbose prints per-file rows and the shared-heartbeat note.
    • skills check lists HEARTBEAT.md — openclaw: pilot block stripped, file kept and openclaw.json — pilotprotocol-prompt-injector: plugin entry removed, file kept. Both files still exist with user text and other keys intact.
    • After skills disable all, skills check says injection is disabled, and --json has disabled: true.

The worktree's pre-commit go vet hook picks up the parent go.work unless it runs with GOWORK=off. It passed once run that way. This is a local setup issue, not something this PR changes.

Deploy notes

  • After merge, daemons pick up skillinject v0.2.4's one-time rewrite of existing marker blocks, which adds r=, and the retired-surface prune on their next start or tick. Disabled hosts are cleaned on the next pilotctl skills check or the post-update tick of pilotctl update.
  • Linux daemons under a systemd unit with Restart=always are now restarted onto new binaries. In every other case restart_error is set, and pilotctl update and update status report it after asking the daemon which version it runs.
  • Follow-up in updater (not needed for this PR to be correct): clear restart_error after a manual run whose restart succeeded when pilot-updater was replaced (status.go recordCheck), and have an up-to-date check on macOS clear it when the daemon runs the installed version.

🤖 Generated with Claude Code

teovl and others added 2 commits September 24, 2026 03:09
…tctl update status)

Bump the three wave-2 libraries (common v0.5.14 and the sigstore-go /
go-openapi versions updater v0.2.5 requires come along via MVS) and do the
web4 follow-ups their PRs call for.

updater#49:
- `pilotctl update` passes StatusPath ~/.pilot/update-state.json, the file
  the pilot-updater loop already writes, so manual runs are recorded.
- RunOnce's error is checked: a failed check exits 1 with code
  update_failed instead of printing success. A successful run reports
  updated vs up to date and warns when restart_error is set; --json adds
  result, updated, current/latest version, restart_error, status_file.
- `pilotctl update status` reads the record: last check, result, error,
  failure streak, versions, last update and "Daemon restart: NEEDED" with
  restart_error. --json adds last_result, last_error, restart_error,
  status_file and the full update_state.

skillinject#39:
- `skills check|enable` and the `update` skills summary count
  ActionRemove and print Outcome.Note (text + --json removes/notes).
- `skills disable all` counts RemovalNeutralized and prints Removal.Note.
- `skills status --json` includes note; --verbose prints it.

dataexchange#43 needs no web4 change: the inbox byte-cap fix and dedupe
are receiver-side and pilotctl sends untagged frames.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aries

Review follow-ups on #468.

F1/F2 - stale restart_error. updater v0.2.5 keeps an earlier
restart_error after a manual run that restarted the daemon (every
release replaces pilot-updater, and recordCheck clears it only when it
did not), and it clears it on a later check only on Linux. LastStatus
merges the record on disk, so `pilotctl update` warned "the daemon is
not running them" right after a successful restart, and every later
run on macOS warned again with `update status` stuck at NEEDED.

pilotctl now asks the daemon over IPC which version it runs (info
"version", bounded to 3s) before it reports anything:
- daemon on the installed version: the record is out of date; no
  warning, `update status` says "not needed".
- daemon on another version: warn, and name a restart command when the
  updater's message has none (its macOS message only names the
  launchctl call that failed).
- no daemon: an update run warns that the daemon is not running and how
  to start it; an up-to-date run says nothing; `update status` says
  "daemon not running".
When an update run leaves restart_error unchanged from before the run,
pilotctl waits up to 30s for a just-restarted daemon to answer. A
restart_error this run recorded is checked once. --json adds
restart_needed, daemon_running and daemon_version; restart_error is ""
when the daemon runs the installed version (update_state keeps the
record as written).

F3 - the fake updater now merges into the on-disk record the way
updater v0.2.5's recordCheck does, and the new tests cover an update
after a failed restart, an up-to-date run on a record holding a
restart_error, and both with no daemon. The tests assert that the
merged record still carries the old restart_error, so they exercise the
stale path.

F4 - main() takes --verbose/-v as the global flag before dispatch, so
`skills status --verbose` never saw it. skills status now honours the
global flag too.

F5 - the reconcile summary said "Removed:" for every retired row. Each
row now says what happened to the file: helpers and plugin files
deleted, the pilot block stripped from a heartbeat file (file kept), the
plugin entry removed from openclaw.json (file kept). Disabled-mode
passes, which only prune retired surfaces, now say injection is
disabled, and --json adds disabled. The update skills line says so too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread cmd/pilotctl/updates.go
}()
select {
case res := <-ch:
d.Close()
Comment thread cmd/pilotctl/updates.go
v, _ := res.info["version"].(string)
return v, true
case <-time.After(daemonProbeTimeout):
d.Close() // unblocks the Info call
@TeoSlayer
TeoSlayer enabled auto-merge (squash) September 24, 2026 01:22
# Conflicts:
#	CHANGELOG.md
#	go.mod
#	go.sum
@TeoSlayer
TeoSlayer merged commit 9adaede into main Sep 24, 2026
15 checks passed
TeoSlayer pushed a commit that referenced this pull request Sep 24, 2026
…ctl update status) into feat/native-https-proxy

Conflicts:
- cmd/pilotctl/updates.go: #468's result fields and printUpdateResult,
  plus the branch's fitTransportToDaemon note for update --pin (JSON
  "note", text "Note:"), with a test that the two coexist.
- CHANGELOG.md: both Fixed lists kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants