* feat: add service-managed self-update
* chore: add temporary self-update test scripts
* chore: simplify temporary script markdown fences
* chore: run frontend in temporary self-update scripts
* fix: show self-update job progress after apply
* chore: add real npm self-update test script
* fix: isolate npm self-update prefix
* fix: detect npm service installs from bin shim
* chore: reset service before real npm update test
* chore: expand real npm status diagnostics
* fix: preserve service home during self-update
* fix(cli): tee self-update helper output to a log file
* fix(cli): avoid launchd bootstrap race when reloading the macOS service
* fix(backend): run macOS self-update helper once via transient launchd plist
* fix(web): show self-update progress and lock the apply button until the update lands
* fix(cli): reinstall via the kandev wrapper on Homebrew self-update
* chore: remove temp self-update test scripts
* fix(web): drop unused apply path from useUpdates hook
* fix(backend): snapshot install state once in apply preflight
* docs: align self-update docs with transient LaunchAgent flow
* chore(release): run cli bundle from apps workspace root
* fix(backend): guard against concurrent self-update applies
* fix(backend): check scheme and helper paths in self-update apply
* fix(backend): drop duplicate binary name from npx manual command
* fix(cli): allow --help for service self-update without --intent
* fix(cli): harden self-update dir perms and darwin uid resolution
* fix(web): set required POST fields last in applyUpdate
* docs: use JSON body notation for the apply endpoint
* fix(backend): expire self-update guard so a stuck helper cannot wedge apply
* refactor(cli): collapse redundant writeSync branch in self-update log
User-mode systemd units and launchd agents now prepend the loading user's
~/.local/bin to PATH, so user-installed agent CLIs (claude, codex, etc.,
installed via npm user prefix, pipx, fnm/nvm) are discoverable by the
kandev daemon. Without this they appeared as `not_installed` even though
they were on the user's interactive PATH.
System-mode units keep the original system-only PATH.
* fix(cli): prepend node bin dir to service unit PATH so npx-based agents resolve
systemd/launchd units hard-coded a system-only PATH. Under per-user node
managers (fnm, nvm, asdf, volta, mise), node lives in a versioned
subdirectory like ~/.local/share/fnm/node-versions/v24.14.0/installation/bin,
so the daemon could exec node (ExecStart resolves the absolute path) but
could not exec npx — every ACP agent built with `npx -y <bridge-pkg>`
(claude, codex, opencode, …) failed at spawn time with "peer disconnected
before response".
`captureLauncher()` already records the node binary path. Its parent dir
is exactly where the matching npm/npx live, so prepend it to the unit's
PATH (with dedup so /usr/local/bin etc. don't appear twice).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(cli): guard pathWithNodeBinDir against non-POSIX nodePath
`path.dirname` returns `'.'` on POSIX when given a path with no `/`
separator (e.g. a Windows-style `C:\…\node.exe`). Prepending `.` to a
service unit's PATH would put CWD ahead of system dirs in the daemon
context — a latent privilege-escalation footgun if any future caller
ever passed a relative or bare-filename nodePath. Production is safe
because `captureLauncher()` uses `process.execPath` (always absolute on
Linux/macOS), so the guard is defensive, not a behavior change.
Adds regression tests for both systemd and launchd, plus a previously
missing launchd system-mode + versioned-bin-dir test.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(release): add repository field for npm provenance
The publish-npm step in release run 25350606233 (v0.39.1) failed for all
6 packages with:
npm error code E422
npm error 422 Unprocessable Entity
Error verifying sigstore provenance bundle: Failed to validate
repository information: package.json: "repository.url" is "",
expected to match "https://github.com/kdlbs/kandev" from provenance
We added `--provenance` to `npm publish` earlier so the registry attaches
a sigstore attestation linking each tarball to its source repo + workflow
run. The attestation is signed using info from the OIDC token (which
includes the repo). At publish time, npm cross-checks the package.json's
`repository.url` against the OIDC repo claim — if the package.json
doesn't have one, or it doesn't match, the publish is rejected.
Both the runtime template (package-npm-runtime.sh) and the main
apps/cli/package.json were missing `repository`. Adding `repository` +
`homepage` to both. The runtime template gets it inline via the
heredoc; the main package.json gets static fields.
This fix unblocks the next release. v0.39.1 itself can't be republished
(version conflict on npm if it had landed; on GitHub release it already
exists). The next release will be v0.39.2 and will go all the way through.
Side state notes:
- v0.39.1 git tag + GitHub release exist with all 5 platform tarballs.
- Homebrew tap was updated to v0.39.1 — `brew install` works as v0.39.1.
- npm has nothing at v0.39.1 (all 6 publishes 422'd).
- v0.39.2 will replace the npm side; Homebrew formula will move forward;
v0.39.1 GitHub release stays as a historical artifact.
* fix(release): re-chmod cli.js after artifact download (lost in zip roundtrip)
`brew install kdlbs/kandev/kandev` followed by `kandev` failed with:
/opt/homebrew/bin/kandev: line 2:
/opt/homebrew/Cellar/kandev/0.39.1/libexec/cli/bin/cli.js: Permission denied
cannot execute: Undefined error: 0
Inspected the published v0.39.1 macos-arm64 tarball:
-rwxr-xr-x kandev/bin/kandev (755 — executable, correct)
-rwxr-xr-x kandev/bin/agentctl (755 — executable, correct)
-rw-r--r-- kandev/cli/bin/cli.js (644 — NOT executable)
Root cause: `actions/upload-artifact@v4` + `actions/download-artifact@v4`
zip-encode the artifact during upload and extract during download.
**Executable bits do not survive that round-trip.** The Go binaries
(kandev, agentctl) keep their +x because they're built inside the same
build-bundles job (no artifact crossing); cli.js is built in build-cli,
uploaded as an artifact, downloaded in build-bundles, and emerges as 644.
The chmod +x in package-cli.sh runs before upload, so it's effectively
erased.
Fix: re-apply chmod +x to dist/kandev/cli/bin/cli.js right before
tarballing, in the Package bundle step (which runs in build-bundles
after the artifact download).
Could not have been caught by the Tier 1.1 local smoke test we did
before merging — that test cp -R'd the cli/ dir directly without an
artifact roundtrip, so the chmod survived. This bug only manifests in
CI's matrix builds.
Manual recovery for v0.39.1 brew users:
chmod +x /opt/homebrew/Cellar/kandev/0.39.1/libexec/cli/bin/cli.js
Or wait for v0.39.2 and `brew upgrade kandev`.