Skip to content

fix: keep the Homebrew symlink in the launchd plist - #37

Merged
vyncint merged 2 commits into
mainfrom
fix/plist-brew-symlink
Aug 8, 2026
Merged

fix: keep the Homebrew symlink in the launchd plist#37
vyncint merged 2 commits into
mainfrom
fix/plist-brew-symlink

Conversation

@vyncint

@vyncint vyncint commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Found while verifying the v0.2.8 cask install end to end on the reference machine.

The plist pinned a version directory

setup resolves symlinks before recording the driver's path, which on a Homebrew install produced:

setup: driver installed as a launchd service … binary=/opt/homebrew/Caskroom/openshell-driver-applecontainer/0.2.8/openshell-driver-applecontainer

That path belongs to one version. The plist now keeps Homebrew's <prefix>/bin symlink, which is stable across versions; every other symlink is still resolved, so the plist does not depend on one staying put. Verified live — setup on a cask install now records /opt/homebrew/bin/openshell-driver-applecontainer.

The Caskroom detection moved to internal/hostsetup and is shared with update, which needs the same answer for the opposite reason (it must not write into the Caskroom).

What testing the upgrade path actually revealed

I first added a postflight launchctl kickstart so a bare brew upgrade would restart the driver on the new binary. Testing it showed the hook can never fire, and why:

brew upgrade removes the driver's launchd service. Homebrew replaces a cask by uninstalling the old version first, and that runs the cask's uninstall launchctl: directive. From cask/artifact/uninstall.rb:

UPGRADE_REINSTALL_SKIP_DIRECTIVES = [:signal].freeze

Only signal is skipped on upgrade/reinstall — launchctl always runs. Confirmed on the machine: after brew reinstall --cask, the agent was gone and ~/Library/LaunchAgents/local.openshell-driver-applecontainer.plist was deleted. The postflight kickstart then had nothing to restart, so it is dropped rather than shipped as dead code.

Keeping the uninstall launchctl: directive is deliberate: dropping it would preserve the service across upgrades, but leave a real brew uninstall with a loaded agent respawning a binary that no longer exists.

So setup after an upgrade is mandatory, not advisory. The README and the cask caveats now say that plainly instead of the weaker "re-run setup so the service restarts on the new binary".

Verification

On the reference machine, through a local tap serving a snapshot build:

  • setup on a cask install records the <prefix>/bin symlink (was: the Caskroom path)
  • cask install with no agent present → postflight succeeds, install completes
  • brew reinstall --cask → agent unloaded and plist deleted, confirming the upgrade behaviour above
  • machine restored to the real v0.2.8 cask and re-setup afterwards

go test ./..., make lint, goreleaser check, and ruby -c on the generated cask all clean. Unit tests cover the cask symlink, an ordinary symlink, and an unresolvable path.

Note: the earlier test (ubuntu-latest) failure on this branch was TestPollerTracksExit in internal/grpcsvc — a timing-sensitive poller test untouched by this change. It passed on rerun and 8 local -race runs; tracked separately.

vyncint added 2 commits August 8, 2026 20:12
setup resolved symlinks before recording the driver's path, so a cask install
wrote Caskroom/openshell-driver-applecontainer/<version>/… into the launchd
plist. `brew upgrade` deletes that directory, so the service would point at a
path that no longer exists and the driver would stay down until setup ran
again — silently, since nothing fails until a sandbox is created.

Keep Homebrew's <prefix>/bin symlink, which outlives every upgrade. Other
symlinks are still resolved, so the plist does not depend on one staying put.

Found on the machine right after installing 0.2.8 from the tap: setup logged
binary=/opt/homebrew/Caskroom/openshell-driver-applecontainer/0.2.8/…

The cask detection now lives in internal/hostsetup and is shared with update,
which needs the same answer for the opposite reason.

Signed-off-by: Vyncint Ng <vyncint@users.noreply.github.com>
setup resolved symlinks before recording the driver's path, so a cask install
wrote Caskroom/openshell-driver-applecontainer/<version>/… into the launchd
plist — a path tied to one version. Keep Homebrew's <prefix>/bin symlink, which
is stable across versions; other symlinks are still resolved, so the plist does
not depend on one staying put.

Also document what testing the upgrade path turned up: `brew upgrade` REMOVES
the driver's launchd service. Homebrew replaces a cask by uninstalling the old
version first, and that runs the cask's `uninstall launchctl:` directive —
UPGRADE_REINSTALL_SKIP_DIRECTIVES skips only `signal`. Keeping the directive is
deliberate: without it a real `brew uninstall` leaves a loaded agent respawning
a binary that no longer exists. So `setup` after an upgrade is mandatory, not
advisory, and the README and caveats now say so plainly.

The cask detection now lives in internal/hostsetup and is shared with update,
which needs the same answer for the opposite reason.

Signed-off-by: Vyncint Ng <vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit cf6ae6d into main Aug 8, 2026
22 checks passed
@vyncint
vyncint deleted the fix/plist-brew-symlink branch August 8, 2026 13:21
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.

1 participant