Skip to content

fix(daemon): preserve Homebrew stable symlink for launchd autostart on macOS - #164

Merged
samueltuyizere merged 2 commits into
routatic:mainfrom
alecchen:fix/launchctl-cellar-pin
Sep 3, 2026
Merged

fix(daemon): preserve Homebrew stable symlink for launchd autostart on macOS#164
samueltuyizere merged 2 commits into
routatic:mainfrom
alecchen:fix/launchctl-cellar-pin

Conversation

@alecchen

@alecchen alecchen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

autostart enable on macOS was saving the wrong binary path.

It resolved the Homebrew symlink with EvalSymlinks, so the plist got /opt/homebrew/Cellar/routatic-proxy/X.Y.Z/... instead of the stable link under /opt/homebrew/bin or /opt/homebrew/opt. Homebrew removes the old Cellar directory on brew upgrade, so the plist pointed at a missing file and launchd failed on next login. The old daemon kept running in RAM (it daemonizes to PPID 1), so curl /health still returned 200 until reboot. That hid the problem.

brew upgrade and brew reinstall never touch the plist. The formula has no post_install or service hook. Only autostart enable writes it.

This fixes it for Homebrew on macOS only. If the binary is under /opt/homebrew or /usr/local, we keep the stable symlink instead of resolving to Cellar. Everything else stays the same, including internal/update/update.go:146.

#92 was closed by #105, but that PR only changed Windows. Same story for #24. There was no test for the Cellar pin.

Changes

  • internal/daemon/paths.go: keep the Homebrew stable path on darwin (new isHomebrewStablePath helper, filepath.Abs instead of EvalSymlinks)
  • internal/daemon/daemon_test.go: new tests for stable path preservation (/opt/homebrew and /usr/local variants) and for isHomebrewStablePath
  • internal/daemon/autostart_darwin_test.go: check that the plist does not contain Cellar/ for Homebrew installs
  • INSTALLATION.md: note that brew upgrade leaves the daemon in RAM until next login, and how to restart it now

Testing

  • gofmt and go vet pass, go test ./... passes (15 in internal/daemon)
  • Manual on Apple Silicon with Homebrew: before, ProgramArguments[0] was .../Cellar/0.6.4/... and reinstall left Cellar/0.6.3 behind. After the fix, autostart disable/enable writes /opt/homebrew/bin/routatic-proxy with no Cellar, launchctl list shows loaded, and /health returns ok.

…n macOS

On macOS, `routatic-proxy autostart enable` wrote the EvalSymlinks-
resolved Cellar/X.Y.Z path into ProgramArguments[0] of
~/Library/LaunchAgents/com.routatic.proxy.plist. Homebrew deletes the
old Cellar on `brew upgrade`/`brew cleanup`, so the plist became
dangling and launchd failed on next login. The running daemon stayed
in RAM via `start --_daemonize` (PPID 1), masking the failure.

Fix: for darwin + Homebrew prefixes (/opt/homebrew, /usr/local),
keep the stable symlink (/opt/homebrew/bin, /opt/homebrew/opt,
/usr/local/bin, /usr/local/opt) via filepath.Abs instead of
EvalSymlinks to Cellar. All other platforms and non-brew macOS
installs keep current behavior; update symlink resolution unchanged.

Tested: overlaid fixed binary to Cellar/0.6.4, `autostart disable/enable`
now writes /opt/homebrew/bin/routatic-proxy (Cellar count 0), launchctl
loaded, health ok. `brew reinstall` correctly leaves stale Cellar/0.6.3
plist until re-enable — fix makes next enable stable.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@alecchen
alecchen marked this pull request as ready for review September 3, 2026 03:24
@kilo-code-bot

kilo-code-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — I didn't think clean PRs existed anymore, but here we are.

Files Reviewed (4 files)
  • internal/daemon/paths.go - targeted fix for Homebrew stable symlink preservation
  • internal/daemon/daemon_test.go - comprehensive tests for new behavior
  • internal/daemon/autostart_darwin_test.go - integration test for launchd plist
  • INSTALLATION.md - clear documentation of brew upgrade behavior

Reviewed by free · Input: 69.6K · Output: 8.6K · Cached: 177.3K

Review guidance: REVIEW.md from base branch main (truncated)

@samueltuyizere
samueltuyizere merged commit bc6cda7 into routatic:main Sep 3, 2026
3 checks passed
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.

2 participants