claude -p --no-session-persistence still runs SessionStart hooks from CWD project, causing hangs
Summary
claude -p --no-session-persistence loads and executes all SessionStart hooks defined in the CWD's .claude/settings.json. When a project has slow or blocking SessionStart hooks (e.g., draining an observation queue via HTTP, checking infrastructure health), claude -p hangs for the duration of those hooks before processing the prompt. This makes claude -p unusable for programmatic dispatch from projects with rich hook configurations.
Reproduction
# From a project directory with SessionStart hooks:
cd ~/my-project # has .claude/settings.json with SessionStart hooks
time claude -p "reply ok" --no-session-persistence --output-format text
# Hangs for 30-60 seconds (sum of hook timeouts), or indefinitely if a hook hangs
# From a directory WITHOUT hooks:
cd /tmp
time claude -p "reply ok" --no-session-persistence --output-format text
# Returns immediately (~2 seconds)
Root Cause Analysis
When claude -p starts from a project directory:
- CWD-based project detection: Claude Code detects
.claude/settings.jsonin the CWD and loads the project configuration, including all hooks - SessionStart hooks fire: All
SessionStarthooks execute sequentially before the prompt is processed --no-session-persistencedoes not skip hooks: The flag controls conversation persistence, not hook execution. Hooks still fire.- Blocking hooks block the entire invocation: If any SessionStart hook hangs (e.g., HTTP call to a slow service),
claude -pblocks for the hook's timeout duration
Measured Impact (real project with 7 SessionStart hooks)
| Hook | Timeout | Actual Duration | Status |
|------|---------|-----------------|--------|
| session-health-gate.sh | 30s | 1.4s | OK |
| observation-capture.py | 10s | 30s (hangs) | Blocks — drains 349 queued observations via HTTP to local LLM |
| memory-inject-l1.py | 8s | 34ms | OK |
| drain-notifications.py | 3s | 26ms | OK |
| wave-summary-bridge.py | 2s | 32ms | OK |
| memory-forgetting-trigger.py | 30s | 70ms | OK |
| auto-dream-trigger.py | 60s | 63ms | OK |
| Total worst case | 143s | ~32s | |
A single blocking hook (observation-capture.py) makes every claude -p invocation wait 30 seconds. With --no-session-persistence, the user expects a fast, stateless invocation — they're explicitly opting out of session state.
Why This Matters
Multi-agent dispatch systems (like Switchyard) invoke claude -p programmatically from the project directory to run headless agents. When SessionStart hooks fire on each invocation, dispatch throughput drops from seconds to minutes. A 5-agent parallel dispatch that should take 2 minutes takes 10+ minutes due to hook overhead.
Expected Behavior
claude -p --no-session-persistence should:
- Skip all SessionStart hooks — the user is requesting a stateless, one-shot invocation
- OR provide a
--skip-hooks/--no-hooksflag to explicitly opt out of hook execution - OR at minimum, run SessionStart hooks with a hard 2-second cap per hook when in print mode (
-p)
Environment
- Claude Code version: 2.1.90
- OS: Linux (OrbStack)
- Node.js: v22.x
- Project: zendev-lite (7 SessionStart hooks, 6 PreToolUse hooks, 7 PostToolUse hooks)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗