Three Launches, Three Native Gates
The helper from the last lesson worked on its first day. The worker did not. On 2026-10-08 the engine tried to start a Claude Code session on the laptop three times, and each attempt stopped at a different gate that belongs to the native CLI, not to Coop.
- An untrusted folder. The first launch exited with "Workspace not trusted. Run
claudein <folder> once and accept the trust prompt." Dad had only ever opened that repository on the laptop through the Desktop app, and the CLI keeps its own trust per folder. - A login. The second launch started a session that stopped at the CLI's own "Login expired · Please run /login". The CLI's credentials are separate from the Desktop app's, so Dad logged the CLI in on that Mac.
- The same login, again. Two minutes after Dad's
/login, the third launch stopped at exactly the same line.
The first two are native gates, and the engine handles each by its shape. A launch the CLI refuses outright, like the untrusted folder, starts nothing, so the assignment shows the native reason and tries the launch again every thirty seconds inside its setup window; Dad accepts the trust prompt once on that Mac and the next attempt goes through. A login gate has a different shape: the session starts, then stops. Discovery reads the background job's state, sees it blocked, and fails the assignment at once with the native reason, because no amount of retrying passes that gate. Dad resolves it on that Mac and assigns again. The engine never answers a trust or login prompt itself. Those gates exist to make a human decide something, and a coordinator that clicks through them has quietly become the human.
The third one was not a gate. It was a measurement waiting to happen.
What the Measurement Said
Dad had just logged in. The CLI said the login had expired. Both can be true only if the process asking is not allowed to see what the login stored, and that is what the numbers showed:
- In a process born in an ssh session,
securityexits 36 with "user interaction is not allowed": it cannot read the user's login keychain. claude auth statussaidloggedIn falseover ssh, andloggedIn truefrom a job in the user'sgui/<uid>launchd domain. Same Mac, same user, same minute.
So the credential was fine. The session the process was born into was wrong. Claude Code keeps its login in the keychain, and an ssh-born process lives outside the security context where that keychain is readable.
That also explained a fact nobody had needed to explain: office's own launches always worked. Coop's runtime on office is a launchd agent in the GUI domain, so everything it spawns is born there already. The laptop launches were the first ones born anywhere else. Codex, for contrast, keeps its login in a plain file under its home directory, which ssh reads without complaint. This particular wall is Claude's alone.
The Fix: Borrow the GUI Session for One Job
Commit 875fddf teaches the helper to run the launch where the keychain is readable. Instead of spawning the command directly, launch_in_gui:
- writes a one-shot launchd job (a property list with a unique label, the command, its working directory,
RunAtLoadandAbandonProcessGroup, and two files for its output); - loads it with
launchctl bootstrap gui/<uid>, which starts it at once in the user's GUI session; - polls
launchctl printevery half second until the job reports it is no longer running and gives its exit code; - unloads it with
bootout, reads the tail of its output, and deletes every temporary file.
AbandonProcessGroup is the quiet hero. When a launchd job exits, launchd normally kills whatever is left in the job's process group. The launch command is claude --bg, whose entire point is to leave a background session running after the command returns. Without that key, the session would die with the job that started it.
And if no GUI session takes the job (nobody is logged in at that Mac, say), bootstrap fails and the helper falls back to a direct spawn. The answer says which context ran, gui or direct, with the reason the GUI path was unavailable, and the journal records it. A fallback that leaves no trace would turn the next "Login expired" back into a mystery.
The First Canary Nobody Typed
With that fix running on office (runtime 875fddf) and Claude Code 2.1.293 on the laptop, canary todo #349 was assigned to the laptop from the workspace. The launch ran as a GUI-domain job; discovery found the new session with its Remote Control bridge; the invitation went out; the session accepted; it reported done with the hostname, fleet label and checkout HEAD the todo asked for. Assignment to report took under a minute. The record says it plainly: no one typed on the laptop.
Notice what that canary proves and what it does not. It proves a fresh session on another Mac can be started, found, admitted and heard from. It does not retroactively prove the first two gates are gone: trust stays per folder and login stays per Mac, and both still stop the next new repository or the next new laptop. Invariant 10 again: each claim gets its own proof.