You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
contextbridge update's post-update harness refresh (added in #52) silently no-ops for every Homebrew-installed user because the spawn target path was deleted by brew before the spawn fires.
runUpdate in packages/cli/src/commands/update.ts calls commandRunner.run(process.execPath, ['install', '<harness-id>', '--scope', '<scope>']) to re-run the new binary's per-harness installer. But process.execPath is captured at process start — for brew-installed binaries this is the cellar path of the pre-upgrade version (e.g., /opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge). Brew's --cask upgrade flow then runs Purging files for version 0.3.0-alpha.1, deleting that whole cellar directory. By the time runUpdate reaches the spawn, the path is gone — Node fails with ENOENT.
The refresh-failure logging from #52 correctly downgrades this to a warning so update itself still exits 0 with ✓ update complete., but the actual per-harness re-install never runs. The post-update refresh feature — the whole reason #52 was filed — is effectively dead for brew users.
Discovered live during alpha.1 → alpha.2 upgrade testing of #62.
contextbridge version
0.3.0-alpha.2 (and 0.3.0-alpha.1; the failure is in the upgrade transition itself, observable on every brew-driven version bump)
Operating system
macOS
Steps to reproduce
Install contextbridge via the brew tap (e.g., brew install contextbridge/tap/cli@alpha).
Run contextbridge install claude --yes so a managed harness exists for the refresh to act on.
Wait for (or simulate) a newer release on the same channel.
Run LOG_LEVEL=trace contextbridge update.
Observe ENOENT warnings for each managed harness while update exits 0.
What did you expect to happen?
After the binary swap, runUpdate re-runs the new binary's install <harness> for each managed scope. Trace should show the per-harness install shellouts firing against the post-upgrade binary path.
What actually happened?
Brew swaps the binary and purges the old cellar directory. runUpdate then tries to posix_spawn against the captured-at-startup process.execPath — which is the now-deleted old path. Each managed harness logs post-update harness refresh failed with ENOENT. Update reports success and exits 0, but no refresh happens.
Relevant logs or stack trace
$ LOG_LEVEL=trace contextbridge update
A new version is available: v0.3.0-alpha.2 (you're on v0.3.0-alpha.1)....==> Upgrading cli@alpha 0.3.0-alpha.1 -> 0.3.0-alpha.2==> Unlinking Binary '/opt/homebrew/bin/contextbridge'==> Linking Binary 'contextbridge' to '/opt/homebrew/bin/contextbridge'==> Purging files for version 0.3.0-alpha.1 of Cask cli@alpha🍺 cli@alpha was successfully upgraded!{"level":40,"msg":"post-update harness refresh failed","err":{"type":"Error","message":"ENOENT: no such file or directory, posix_spawn '/opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge'","code":"ENOENT","path":"/opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge","syscall":"posix_spawn","errno":-2},"harness":"claude","scope":"user"}{"level":40,"msg":"post-update harness refresh failed","err":{"type":"Error","message":"ENOENT: no such file or directory, posix_spawn '/opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge'","code":"ENOENT","path":"/opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge","syscall":"posix_spawn","errno":-2},"harness":"codex","scope":"user"}✓ update complete.
which contextbridge immediately after returns /opt/homebrew/bin/contextbridge (the stable symlink), which readlink resolves to …/0.3.0-alpha.2/contextbridge. The new binary is in place and would have worked if it had been the spawn target.
Why the unit tests in packages/cli/src/commands/update.test.ts don't catch this: FakeCommandRunner happily resolves stubs registered against process.execPath, so executed → refresh fires → install spawns looks fine in tests. The cellar-purge happens outside the test process.
Affected scope: every brew-installed user (likely the majority of macOS users).
Install-script users may be unaffected if the install script writes the binary to a stable path that survives upgrades — worth verifying, but the brew path is definitely broken.
What happened?
contextbridge update's post-update harness refresh (added in #52) silently no-ops for every Homebrew-installed user because the spawn target path was deleted by brew before the spawn fires.runUpdateinpackages/cli/src/commands/update.tscallscommandRunner.run(process.execPath, ['install', '<harness-id>', '--scope', '<scope>'])to re-run the new binary's per-harness installer. Butprocess.execPathis captured at process start — for brew-installed binaries this is the cellar path of the pre-upgrade version (e.g.,/opt/homebrew/Caskroom/cli@alpha/0.3.0-alpha.1/contextbridge). Brew's--caskupgrade flow then runsPurging files for version 0.3.0-alpha.1, deleting that whole cellar directory. By the timerunUpdatereaches the spawn, the path is gone — Node fails withENOENT.The refresh-failure logging from #52 correctly downgrades this to a warning so
updateitself still exits 0 with✓ update complete., but the actual per-harness re-install never runs. The post-update refresh feature — the whole reason #52 was filed — is effectively dead for brew users.Discovered live during alpha.1 → alpha.2 upgrade testing of #62.
contextbridge version
0.3.0-alpha.2 (and 0.3.0-alpha.1; the failure is in the upgrade transition itself, observable on every brew-driven version bump)
Operating system
macOS
Steps to reproduce
brew install contextbridge/tap/cli@alpha).contextbridge install claude --yesso a managed harness exists for the refresh to act on.LOG_LEVEL=trace contextbridge update.ENOENTwarnings for each managed harness while update exits 0.What did you expect to happen?
After the binary swap,
runUpdatere-runs the new binary'sinstall <harness>for each managed scope. Trace should show the per-harness install shellouts firing against the post-upgrade binary path.What actually happened?
Brew swaps the binary and purges the old cellar directory.
runUpdatethen tries toposix_spawnagainst the captured-at-startupprocess.execPath— which is the now-deleted old path. Each managed harness logspost-update harness refresh failedwithENOENT. Update reports success and exits 0, but no refresh happens.Relevant logs or stack trace
which contextbridgeimmediately after returns/opt/homebrew/bin/contextbridge(the stable symlink), whichreadlinkresolves to…/0.3.0-alpha.2/contextbridge. The new binary is in place and would have worked if it had been the spawn target.Anything else
refresh plugins on update) —runUpdatewalksALL_INSTALLERSand spawnsprocess.execPath install <harness-id>for each managed scope.packages/cli/src/commands/update.test.tsdon't catch this:FakeCommandRunnerhappily resolves stubs registered againstprocess.execPath, soexecuted → refresh fires → install spawnslooks fine in tests. The cellar-purge happens outside the test process.