anthropics / anthropics/claude-code

[BUG] `claude -p` under macOS launchd takes ~143s per call (plugin autoupdate + async SessionStart hook wait before result); 3s from a terminal

Open
#95,689 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:core area:hooks area:plugins bug has repro platform:macos
Dominant language
TypeScript
Stars
147k
Forks
24k
PR merge metrics
PR metrics pending

Description

Summary

claude -p (print mode, tools off, empty MCP map) takes ~143 s for a one-word answer when the process is started by macOS launchd, versus ~3 s for the identical command from a terminal. The model round trip is ~2 s in both cases; the rest is user-level settings work around the turn. Passing --setting-sources project from an empty directory (so no user hooks/plugins load) brings the launchd run down to 2 s and the keychain login still works.

The result text is not printed until this work finishes, so scheduled jobs that wrap claude -p in a subprocess timeout (ours: 300 s, real prompts ~25k tokens) time out every call while the same script works when run by hand.

Environment

  • Claude Code 2.1.267 (native arm64 binary, ~/.local/bin/claude), macOS 25.6.0 (Darwin), Apple Silicon
  • Logged in with claude.ai (Max) OAuth; ANTHROPIC_API_KEY unset
  • User settings enable several plugins (superpowers, security-guidance, i-have-adhd, impeccable, ui-design, last30days) and a handful of command hooks

Repro

Throwaway LaunchAgent (any label), RunAtLoad, running:

source "$HOME/.bash_profile"; unset ANTHROPIC_API_KEY
claude -p --debug-file /tmp/cc-launchd.debug.log --model sonnet --no-session-persistence \
  --tools "" --strict-mcp-config --mcp-config '{"mcpServers":{}}' --output-format text \
  --system-prompt "You answer in one word." "Say the word ready." </dev/null >/tmp/out.txt 2>&1
  • launchctl bootstrap gui/$(id -u) that.plistrc=0 elapsed=143s
  • Same command line in a terminal (also with env -i HOME=… PATH=…) → rc=0 elapsed=3–5s
  • Same LaunchAgent with --setting-sources project added and WorkingDirectory set to an empty dir → rc=0 elapsed=2s, answer Ready.
  • --bare is not a workaround: it skips keychain reads, so it answers Not logged in · Please run /login.

Debug log timeline (launchd run, gaps > 4 s)

GAP  5s after: File ~/.claude.json written atomically
        next: [skills] failed to read ~/.claude/skills/<x>/SKILL.md: Error: EINTR: interrupted system call
GAP  5s after: installPluginsForHeadless: starting
GAP 29s after: "Hook Stop (Stop) success: …"            (all Stop hooks finished here)
        next: plugin autoupdate: checking installed plugins
GAP 17s after: Fetching manifest version for figma@claude-plugins-official: 2.2.111
        next: Preserving file permissions … (~/.claude.json write)
GAP 63s after: [event-loop-stall] blocked for 610ms …
        next: [engine] turn 1 end (turns=1 usage in=2 out=6 api=1948ms stop=end_turn resultLen=6)
        then: "Hook SessionStart:startup (SessionStart) success: {"async": true, "asyncTimeout": 180000} …"
              [uds-messaging] Shutting down … result printed, exit

api=1948ms for the actual request. The 63 s gap ends exactly when an async SessionStart hook (security-guidance's ensure_agent_sdk.py, which answers {"async": true, "asyncTimeout": 180000}) is logged as complete, so print mode appears to wait for async SessionStart hooks before emitting the result. The hook script itself finishes in <1 s when run directly under launchd, so the wait is not the hook's own runtime. In a second launchd run the same phase logged Lock acquisition took longer than expected - another Claude instance may be running (43 s) before the ~/.claude.json write.

Expected

In -p mode the result should be printed as soon as the turn ends; plugin auto-update and async SessionStart hooks should not sit between the model response and stdout. At minimum, a flag or env to skip plugin auto-update and async hook waits in print mode without giving up the keychain login (--bare currently bundles those together).

Workaround we shipped

--setting-sources project with cwd set to an empty temp dir, for every headless call made from a scheduled job.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the claude -p LaunchAgent command from the issue and inspect /tmp/cc-launchd.debug.log, comparing it with the terminal and --setting-sources project runs. Trace the gaps around plugin autoupdate, the async SessionStart hook, and the turn result; done means print mode emits the result after the turn without those waits while preserving keychain login.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, shell, typescript
Domain
cli, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.