Skip to content

fix(launchd): tolerate bootout EIO for not-loaded agents on macOS#10965

Open
hsbt wants to merge 1 commit into
jdx:mainfrom
hsbt:fix-launchd-bootout-eio
Open

fix(launchd): tolerate bootout EIO for not-loaded agents on macOS#10965
hsbt wants to merge 1 commit into
jdx:mainfrom
hsbt:fix-launchd-bootout-eio

Conversation

@hsbt

@hsbt hsbt commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Thanks for mise bootstrap — the launchd-agents support is exactly what I'd been wanting, and I ran into this while migrating my own agents to it.

mise bootstrap macos launchd-agents apply fails on a cold machine where the agents aren't loaded yet. mise runs launchctl bootout before launchctl bootstrap for each agent, but on current macOS (Darwin 27) launchctl bootout gui/<uid> <plist> for a not-loaded service returns Boot-out failed: 5: Input/output error instead of No such process. bootout_missing_error in src/system/launchd.rs doesn't recognize this, so apply aborts after writing only the first plist. In my case it wrote only ~/Library/LaunchAgents/dev.mise.GOPATH.SetEnv.plist before erroring.

This treats the code-5 (EIO) not-loaded case as ignorable, scoped to boot-out failed: 5 so unrelated failures such as Boot-out failed: 1: Operation not permitted stay fatal. bootout is a best-effort unload right before bootstrap, so a genuine problem still surfaces on the following bootstrap/enable call; this just makes a first-time apply idempotent on current macOS.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling when stopping services that are already unavailable.
    • Prevented certain expected service-stop errors from being reported as unexpected failures.

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>
Copilot AI review requested due to automatic review settings July 13, 2026 05:25
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

bootout_missing_error now treats boot-out failed: 5 as a missing service condition on macOS. Its unit test verifies this behavior and confirms boot-out failed: 1 remains an unhandled error.

Changes

launchd bootout handling

Layer / File(s) Summary
Handle missing-service bootout errors
src/system/launchd.rs
The matcher recognizes Darwin’s boot-out failed: 5 response as missing, with tests covering both the accepted code 5 and rejected code 1 cases.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: copilot

Poem

A rabbit taps bootout twice,
Finds code five and calls it nice.
Code one stays beneath the gate,
Tests keep watch and mark its fate.
Hop, hop—launchd’s path is clear!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main macOS launchd bootout behavior change and matches the updated EIO handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 bootout failures with Boot-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.

Comment thread src/system/launchd.rs
Comment on lines +496 to +498
// 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-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes mise bootstrap macos launchd-agents apply on macOS Darwin 27, where launchctl bootout of a not-loaded service returns Boot-out failed: 5: Input/output error instead of No such process. The fix teaches bootout_missing_error to treat that EIO response as an ignorable "service not loaded" condition.

  • Adds || error.contains(\"boot-out failed: 5\") to the allow-list of ignorable bootout errors in src/system/launchd.rs, and updates the test to assert this case is treated as non-fatal while Boot-out failed: 1: Operation not permitted remains fatal.
  • The substring \"boot-out failed: 5\" is slightly too broad: it also matches \"boot-out failed: 50\", \"boot-out failed: 51\", etc., meaning error codes in the 50–59 range would be silently ignored as well.

Confidence Score: 4/5

Safe 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

Filename Overview
src/system/launchd.rs Adds "boot-out failed: 5" as an ignorable bootout error; the substring match is slightly too broad and could swallow error codes 50-59.

Reviews (1): Last reviewed commit: "fix(launchd): tolerate bootout EIO for n..." | Re-trigger Greptile

Comment thread src/system/launchd.rs
|| 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Suggested change
|| error.contains("boot-out failed: 5")
|| error.contains("boot-out failed: 5: input/output error")

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f78506 and 953f4e5.

📒 Files selected for processing (1)
  • src/system/launchd.rs

Comment thread src/system/launchd.rs
Comment on lines +496 to +498
// 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
// 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.

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