`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

Status Open
Reported on v2.1.163
Maintainer reply None cached
Activity 1 comment · opened Jul 18, 2026

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 -l sourcing 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 -p hang, interactive fine) but carried no diagnosis and was auto-closed as a duplicate of #61416.
  • #61416 is a different bug: claude -p failing 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗