`claude -p` hangs before first token when launched from a macOS launchd job — main thread parked in kevent64 waiting on the `/bin/zsh -c -l` shell-snapshot child; `SHELL=/bin/sh` works around it
Summary
Running claude -p from a macOS launchd LaunchAgent hangs at startup: the process emits nothing (0-byte output log, no first token) for 10+ minutes until our watchdog kills it. The identical invocation from an interactive terminal completes normally every time. Forcing SHELL=/bin/sh for the invocation removes the hang entirely.
Environment
- macOS (Darwin 25.x, Apple Silicon Mac mini)
- Onset with Claude Code 2.1.20x (first seen 2026-07-08); mitigated since 2026-07-13, so we have not re-tested unmitigated on current versions
- Subscription auth (not Bedrock)
- Invocation:
claude -p "<prompt>" --model <model> --strict-mcp-config --settings <file> --debug-file <file> </dev/null, from a nightly launchd LaunchAgent - The session's tool allowlist contains no Bash tool, so the shell snapshot is never even consumed by the session that pays for it
What we captured while it hung
- A sample of the hung CLI process shows the main thread parked in
kevent64. - The only outstanding child is the shell-snapshot generator,
/bin/zsh -c -lsourcing our~/.zshrc(a heavy one: nvm, brew shellenv, completion init, no interactivity guard). - Keychain unlocked, API reachable, no MCP servers (
--strict-mcp-config). - The obvious environmental difference from an interactive run is launchd's context: no TTY, minimal environment.
Verified workaround
SHELL=/bin/sh claude -p ... makes the CLI snapshot a near-empty POSIX shell instead of the heavy zsh init. The hang disappeared immediately and every nightly run since 2026-07-13 has been green. We found no mention of SHELL as a supported knob in the docs or changelog, so this relies on undocumented behaviour that could regress in any release.
Hypothesis, explicitly untested: heavy .zshrc initialisation stalls snapshot generation under launchd's non-interactive context. We have an A/B planned (heavy vs guarded .zshrc under launchd) and can report results here.
Why this is not a duplicate
- #69173 described the same symptom (launchd +
claude -phang, interactive fine) but carried no diagnosis and was auto-closed as a duplicate of #61416. - #61416 is a different bug:
claude -pfailing to exit after a backgrounded Bash task completes. That is exit-side, after a result exists. Our hang is startup-side, before the first token, so exit-side fixes (for example the 2.1.163 background-shell grace period) cannot touch it.
Ask
Root-cause the shell-snapshot path under launchd, or document SHELL as a supported control for headless invocations. We can supply full --debug-file output and stack samples from the hung process on request.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗