fix(launchd): tolerate bootout EIO for not-loaded agents on macOS#10965
fix(launchd): tolerate bootout EIO for not-loaded agents on macOS#10965hsbt wants to merge 1 commit into
Conversation
macOS (Darwin 27) returns `Boot-out failed: 5: Input/output error` from `launchctl bootout` when the target service is not currently loaded, instead of `No such process`. `bootout` is a best-effort unload run right before `bootstrap`, so treating this EIO as fatal aborted `bootstrap ... apply` on a cold machine after writing only the first plist. Recognize the code-5 (EIO) not-loaded case as ignorable; a real problem still surfaces on the following `bootstrap`/`enable` call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthrough
Changeslaunchd bootout handling
Estimated code review effort: 1 (Trivial) | ~5 minutes 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 |
There was a problem hiding this comment.
Pull request overview
Improves macOS launchd agent bootstrapping reliability by making launchctl bootout a best-effort step when the service is not yet loaded on newer macOS versions (Darwin 27), so mise bootstrap macos launchd-agents apply is idempotent on cold machines.
Changes:
- Treat
launchctl bootoutfailures withBoot-out failed: 5(EIO on Darwin 27 for not-loaded services) as ignorable in the “missing service” classifier. - Extend unit tests to cover the EIO case as ignorable and an “Operation not permitted” case as still-fatal.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // macOS (Darwin 27) returns EIO for `launchctl bootout` of a | ||
| // service that is not currently loaded, not "No such process". | ||
| || error.contains("boot-out failed: 5") |
Greptile SummaryThis PR fixes
Confidence Score: 4/5Safe to merge with a minor fix — the logic is correct in intent, but the string check should be tightened before landing. The fix correctly identifies and ignores the EIO not-loaded case on Darwin 27, and the complementary test for the Operation not permitted case is a good addition. However, the contains check on boot-out failed: 5 is a prefix-substring match that would also silently absorb errors coded 50-59, which are distinct POSIX conditions. While bootout is a best-effort step, masking these codes makes debugging harder if they ever appear in practice. src/system/launchd.rs — the string match in bootout_missing_error needs to be made more specific. Important Files Changed
Reviews (1): Last reviewed commit: "fix(launchd): tolerate bootout EIO for n..." | Re-trigger Greptile |
| || error.contains("not in domain") | ||
| // macOS (Darwin 27) returns EIO for `launchctl bootout` of a | ||
| // service that is not currently loaded, not "No such process". | ||
| || error.contains("boot-out failed: 5") |
There was a problem hiding this comment.
The substring
"boot-out failed: 5" matches any error code that starts with 5 (e.g., 50, 51, 52), so Boot-out failed: 50: Network is down or similar real errors would be silently treated as "service not loaded" and swallowed. Adding the trailing colon-space makes the match precise to the exact EIO code.
| || error.contains("boot-out failed: 5") | |
| || error.contains("boot-out failed: 5: input/output error") |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/system/launchd.rs`:
- Around line 496-498: Update the launchd error matching logic around the
bootout handling to match the complete numeric error-code token for code 5,
including its delimiter or by parsing the code, so values such as 50 are not
treated as code 5. Add a regression case covering a prefixed non-5 code and
verify it is not suppressed.
🪄 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: 38f14ffa-79aa-474e-acec-3b5d9753e571
📒 Files selected for processing (1)
src/system/launchd.rs
| // macOS (Darwin 27) returns EIO for `launchctl bootout` of a | ||
| // service that is not currently loaded, not "No such process". | ||
| || error.contains("boot-out failed: 5") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Match the complete error-code token.
This substring also matches boot-out failed: 50 and similar values, which could incorrectly suppress unrelated failures. Match the delimiter after 5 (or parse the numeric code) and add a regression case for a non-5 code sharing the prefix.
Proposed fix
- || error.contains("boot-out failed: 5")
+ || error.contains("boot-out failed: 5:")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // macOS (Darwin 27) returns EIO for `launchctl bootout` of a | |
| // service that is not currently loaded, not "No such process". | |
| || error.contains("boot-out failed: 5") | |
| // macOS (Darwin 27) returns EIO for `launchctl bootout` of a | |
| // service that is not currently loaded, not "No such process". | |
| || error.contains("boot-out failed: 5:") |
🤖 Prompt for 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.
In `@src/system/launchd.rs` around lines 496 - 498, Update the launchd error
matching logic around the bootout handling to match the complete numeric
error-code token for code 5, including its delimiter or by parsing the code, so
values such as 50 are not treated as code 5. Add a regression case covering a
prefixed non-5 code and verify it is not suppressed.
Thanks for
mise bootstrap— thelaunchd-agentssupport is exactly what I'd been wanting, and I ran into this while migrating my own agents to it.mise bootstrap macos launchd-agents applyfails on a cold machine where the agents aren't loaded yet. mise runslaunchctl bootoutbeforelaunchctl bootstrapfor each agent, but on current macOS (Darwin 27)launchctl bootout gui/<uid> <plist>for a not-loaded service returnsBoot-out failed: 5: Input/output errorinstead ofNo such process.bootout_missing_errorinsrc/system/launchd.rsdoesn't recognize this, soapplyaborts after writing only the first plist. In my case it wrote only~/Library/LaunchAgents/dev.mise.GOPATH.SetEnv.plistbefore erroring.This treats the code-5 (EIO) not-loaded case as ignorable, scoped to
boot-out failed: 5so unrelated failures such asBoot-out failed: 1: Operation not permittedstay fatal.bootoutis a best-effort unload right beforebootstrap, so a genuine problem still surfaces on the followingbootstrap/enablecall; this just makes a first-timeapplyidempotent on current macOS.Summary by CodeRabbit