feat(boot): auto-heal stale boot registration on supervisor startup - #707
Conversation
When the binary path changes after a package-manager upgrade (e.g. brew cleanup, mise install), the launchd plist or systemd unit still points to the old version-specific path, causing the supervisor to fail on next boot. On supervisor startup, check whether the registered boot binary path matches the current PITCHFORK_BIN. If stale, re-register with the current path. This runs in a background spawn_blocking task and is best-effort: errors are logged and swallowed, never blocking startup. Requires auto-launcher 1.1 which adds get_registered_app_path(). Closes jdx#544
📝 WalkthroughWalkthroughThe update adds stale boot-registration recovery. ChangesBoot registration recovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Supervisor
participant BootManager
participant auto-launcher
Supervisor->>BootManager: Initialize during startup
BootManager->>auto-launcher: Read registered executable path
auto-launcher-->>BootManager: Return registered path
BootManager->>auto-launcher: Re-register when the path is stale
BootManager-->>Supervisor: Finish without interrupting startup
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThe PR adds best-effort repair of stale boot registrations when the supervisor starts.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported registration-loss path was removed by no longer disabling the existing registration before attempting replacement. Important Files Changed
Reviews (2): Last reviewed commit: "fix(boot): overwrite stale registration ..." | Re-trigger Greptile |
…ng it Greptile review (jdx#707): calling disable() before enable() risks losing the boot registration entirely if enable() fails. Since enable() already overwrites the on-disk file on all platforms, skip disable() so a failed re-register leaves the stale path intact (stale path > no path).
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/boot_manager.rs`:
- Around line 253-257: Update the repair path around self.current.enable() to
call BootManager::enable() via self.enable() directly, without first calling
disable(). Preserve the existing error handling and stale-registration behavior
while ensuring privilege-level validation and macOS legacy cleanup run during
repair.
- Around line 253-257: Update the registration write flow used by
BootManager::current.enable() to write the new contents to a temporary file and
atomically rename it over the existing registration, preserving the old file if
writing fails; alternatively extend the dependency contract with equivalent
backup-and-restore behavior. Ensure the fallback around this enable call still
retains the prior valid registration on any write error.
In `@src/supervisor/mod.rs`:
- Around line 385-387: Update the unsupported-platform BootManager
implementation in boot_manager.rs to define check_and_reregister_if_stale with
the same callable interface as the supported implementations, using a no-op body
so Supervisor::start() type-checks on all targets while BootManager::new()
continues returning its existing error.
- Around line 385-388: Update the BootManager initialization inside the
spawn_blocking closure to match on BootManager::new() instead of discarding Err
values; preserve the existing stale-registration check on success, and log the
initialization error on failure before continuing startup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 7fccd405-a8e0-42ec-949f-50c1f3906d56
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
Cargo.tomlsrc/boot_manager.rssrc/supervisor/mod.rs
## 🤖 New release * `pitchfork-cli`: 2.19.0 -> 2.20.0 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [2.20.0](v2.19.0...v2.20.0) - 2026-08-02 ### Added - *(supervisor)* expand ~ in daemon dir using effective user's home ([#708](#708)) - *(boot)* auto-heal stale boot registration on supervisor startup ([#707](#707)) - *(web)* structured log display with filters in webui ([#630](#630)) - *(supervisor)* allow disabling client auto-start ([#678](#678)) - *(supervisor)* add --json flag to supervisor status ([#601](#601)) - *(config)* expand home-relative paths ([#675](#675)) - *(logs)* let the sink serve on_output hooks ([#668](#668)) - *(usage)* declare what each command does to the world ([#666](#666)) - *(logs)* let the sink decide ready_output ([#667](#667)) ### Fixed - deterministic IPC response attribution and whole-group stop wait ([#606](#606)) - *(config)* include registries in schema ([#671](#671)) ### Other - *(deps)* update rust crate libc to v0.2.189 ([#703](#703)) - *(deps)* update rust crate clap to v4.6.4 ([#702](#702)) - *(deps)* update rust crate glob to v0.3.4 ([#701](#701)) - *(deps)* update rust crate tokio-util to v0.7.19 ([#699](#699)) - *(deps)* update rust crate libc to v0.2.188 ([#698](#698)) - *(deps)* update rust crate syn to v3 ([#697](#697)) - *(deps)* update rust crate hyper to v1.11.0 ([#695](#695)) - *(deps)* update rust crate tokio to v1.53.1 ([#692](#692)) - *(deps)* update rust crate thiserror to v2.0.19 ([#691](#691)) - *(deps)* update rust crate serde to v1.0.229 ([#689](#689)) - *(deps)* update rust crate quote to v1.0.47 ([#687](#687)) - *(deps)* update rust crate serde_json to v1.0.151 ([#690](#690)) - *(deps)* update rust crate libc to v0.2.187 ([#685](#685)) - *(deps)* update rust crate proc-macro2 to v1.0.107 ([#686](#686)) - *(deps)* update rust crate clx to v3.0.2 ([#684](#684)) - *(deps)* update rust crate clap to v4.6.3 ([#683](#683)) - *(deps)* use rust-aware cargo resolver ([#677](#677)) - *(deps)* update clx ([#672](#672)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Metadata-only release bump and changelog; no behavioral changes in the diff itself. > > **Overview** > Bumps **pitchfork-cli** from **2.19.0** to **2.20.0** in `Cargo.toml`, `Cargo.lock`, and generated CLI metadata (`pitchfork.usage.kdl`, `docs/cli/commands.json`, `docs/cli/index.md`). > > Adds a **CHANGELOG** section for **2.20.0** that records what shipped since 2.19.0 (supervisor path expansion, boot auto-heal, web UI log filters, IPC/stop fixes, config schema, dependency updates, etc.). This PR does not implement those features—it only publishes the release notes and version strings for tagging/publishing. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d7d9c08. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Problem
When the binary path changes after a package-manager upgrade (e.g.
brew cleanup,mise install), the launchd plist or systemd unit still points to the old version-specific path, causing the supervisor to fail on next boot withEX_CONFIG (78)on macOS or a missing binary on Linux.See discussion #544 for details.
Solution
On supervisor startup, check whether the registered boot binary path matches the current
PITCHFORK_BIN. If stale, re-register with the current path.This runs in a background
spawn_blockingtask and is best-effort: errors are logged and swallowed, never blocking startup.Changes
Cargo.toml: upgradeauto-launcherfrom 1.0.3 to 1.1src/boot_manager.rs: addcheck_and_reregister_if_stale()which reads the registered path back from disk viaauto_launcher::AutoLaunch::get_registered_app_path()(new in 1.1), compares toPITCHFORK_BIN, and re-registers bydisable()+enable()when stalesrc/supervisor/mod.rs: call the check inSupervisor::start()viatokio::task::spawn_blocking, fire-and-forgetWhy not just use a stable path?
mise shims are shell scripts that require
MISE_*environment variables which launchd/systemd minimal environments cannot provide. Detect-and-re-register avoids this issue entirely and works across all package managers.auto-launcher dependency
This PR requires
auto-launcher1.1, which addsget_registered_app_path()— a new API that reads the binary path back from the on-disk launch file (plist/systemd unit/registry). See auto-launcher PR #7.Closes #544
AI-assisted — Tool: opencode; model: astra/glm_5d2_fp8_code; version: unavailable.
Summary by CodeRabbit
Bug Fixes
Maintenance