Skip to content

fix(settings): cover slow SessionEnd hooks with CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS#1308

Open
jacobo-ortiz wants to merge 1 commit into
danielmiessler:mainfrom
jacobo-ortiz:fix/sessionend-chain-timeout
Open

fix(settings): cover slow SessionEnd hooks with CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS#1308
jacobo-ortiz wants to merge 1 commit into
danielmiessler:mainfrom
jacobo-ortiz:fix/sessionend-chain-timeout

Conversation

@jacobo-ortiz
Copy link
Copy Markdown

Problem

Claude Code v2.1.74+ caps the entire SessionEnd hook chain at 1.5 seconds by default (Claude Code docs; originally surfaced via anthropics/claude-code#33706). The per-hook timeout field (default 600s) is bounded by this chain-wide budget, not in addition to it.

PAI v5.0.0 ships UpdateCounts.hook.ts in SessionEnd position 4. Its handler (hooks/handlers/UpdateCounts.ts) makes two synchronous Anthropic API calls inside refreshUsageCache:

  • usage OAuth API — AbortSignal.timeout(3000) at line 214
  • cost_report admin API — AbortSignal.timeout(5000) at line 234 (only when ANTHROPIC_ADMIN_API_KEY is set)

Worst case: ~8 seconds. Measured locally on macOS, 5 runs: 3.05 / 3.72 / 7.42 / 6.46 / 3.74 seconds. UpdateCounts alone blows the 1.5s SessionEnd budget by 2–5×, producing the recurring output:

SessionEnd hook [$HOME/.claude/hooks/UpdateCounts.hook.ts] failed: Hook cancelled

When the chain budget expires mid-execution, subsequent hooks (IntegrityCheck, KVSync) never start — statusline counts go stale and Cloudflare KV sync is skipped.

Why this is structural, not a bug in UpdateCounts

  • UpdateCounts.hook.ts and handlers/UpdateCounts.ts are byte-identical to upstream v5.0.0 — confirmed via diff.
  • Standalone timing (/usr/bin/time -p × 5) shows 3.05 to 7.42 s — the API calls are the bottleneck regardless of session state.
  • Sibling SessionEnd hooks measured for comparison: IntegrityCheck 0.60 s, KVSync 0.91 s.

The fix

Add CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=10000 to the env block in Releases/v5.0.0/.claude/settings.json. 10000 ms gives UpdateCounts worst-case (~8 s) headroom while keeping the chain bounded.

   "env": {
     "PAI_DIR": "${HOME}/.claude/PAI",
     "PROJECTS_DIR": "${HOME}/Projects",
     "BASH_DEFAULT_TIMEOUT_MS": "600000",
     "API_TIMEOUT_MS": "1800000",
+    "CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS": "10000",
     "PAI_CONFIG_DIR": "${HOME}/.claude/PAI",
     "GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE": ...
   },

Verification

  • jq '.env.CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS' settings.json"10000"
  • jq empty settings.json → JSON valid (no parse errors)
  • After a clean session exit, no Hook cancelled line for UpdateCounts / IntegrityCheck / KVSync.

Related

Notes for maintainers

  • If a more conservative value is preferred (e.g., 5000 ms, matching the Claude Code docs example), I'm happy to adjust. 10000 ms is chosen specifically to cover UpdateCounts worst case with margin; 5000 ms would leave the cost_report API path at risk on slow networks.
  • Long-term, moving refreshUsageCache to a detached subprocess (returning the hook in <50 ms) would eliminate the budget pressure entirely, but it's a larger change. The env-var addition is the minimal fix that resolves the symptom for every PAI user today.

…over slow SessionEnd hooks

Claude Code v2.1.74+ caps the entire SessionEnd hook chain at 1.5
seconds by default (documented at https://code.claude.com/docs/en/hooks).
The v5.0.0 SessionEnd chain includes UpdateCounts.hook.ts, which makes
two synchronous Anthropic API calls inside refreshUsageCache (usage API
with 3s timeout + cost_report API with 5s timeout, worst case ~8s).
UpdateCounts alone blows the 1.5s budget by 2-5x, producing recurring
terminal output:

    SessionEnd hook $HOME/.claude/hooks/UpdateCounts.hook.ts failed: Hook cancelled

When the budget expires, subsequent hooks (IntegrityCheck, KVSync)
never start — statusline counts and Cloudflare KV sync go stale.

Setting CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=10000 (10 seconds)
gives the chain enough budget to complete UpdateCounts plus its
neighbors (IntegrityCheck 0.6s + KVSync 0.9s measured locally) with
headroom.

Related: danielmiessler#965
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant