Hook authoring gotchas: Auto Mode swallows `permissionDecision: "ask"`; SessionStart re-fire semantics underdocumented

Resolved 💬 4 comments Opened May 24, 2026 by ndany Closed Jun 27, 2026

Summary

Two undocumented hook behaviors cost significant discovery time while building production hooks (one PreToolUse, two SessionStart). Both are common cases future hook authors will hit. Documenting them in the official hook authoring guide (and the update-config built-in skill) would help.

1. Auto Mode silently auto-accepts permissionDecision: "ask"

A PreToolUse hook that returns {"hookSpecificOutput": {"permissionDecision": "ask", "permissionDecisionReason": "..."}} is documented to surface a confirmation prompt to the user before the tool runs.

Observed in Auto Mode sessions: the prompt is silently auto-accepted; the tool proceeds without user interaction. Verified via transcript inspection — the hook fired, output well-formed JSON with permissionDecision: "ask", and ~0.5s later the tool call completed normally with no user-visible prompt.

Why this matters: hook authors expect three semantic options (allow, ask, deny). In Auto Mode, the middle option (ask = "warn but let user choose") effectively collapses into allow. The only way to ensure a hook actually blocks is deny — which is fail-closed and removes per-action user choice. The intended "soft gate" semantic is not available in Auto Mode sessions.

Workaround: use permissionDecision: "deny" for any hook where false-negative on enforcement is unacceptable.

Repro:

  1. Add a PreToolUse hook on Bash matcher whose command outputs {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"test"}}
  2. Start a session in Auto Mode
  3. Trigger any Bash command from Claude
  4. Expected: confirmation prompt; observed: tool runs immediately

Suggestions:

  • Document this clearly in the hook authoring guide and update-config skill
  • Consider whether hook output should be able to opt an action out of Auto Mode auto-acceptance (e.g., a requireUserConfirmation: true field), so authors can choose "warn + ask" semantics that survive Auto Mode

2. SessionStart hooks don't re-fire on in-process conversation continuation

SessionStart hooks reliably fire on:

  • Fresh session (SessionStart:startup)
  • Session resume via full CLI process restart (SessionStart:resume)

They do NOT re-fire on:

  • "New conversation" within the same VS Code window
  • VS Code "Developer: Reload Window"

Implication for hook development: iterating on a SessionStart hook requires fully quitting and re-opening Claude Code (or restarting the CLI process) to test each change. Reload-window is not sufficient.

Without transcript inspection (grepping ~/.claude/projects/<encoded-cwd>/<session>.jsonl for hook_success records with hookEventName: SessionStart), there's no visible signal that the updated hook hasn't been picked up — failing tests look identical to "hook fired but did the right thing of nothing."

Repro:

  1. Add a SessionStart hook that outputs {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"v1"}}
  2. Verify v1 appears in additionalContext on a fresh session
  3. Edit the hook to output "v2"
  4. Reload VS Code window or start new conversation in same window → still v1
  5. Fully quit + re-open Claude Code → v2 appears

Suggestions:

  • Document the re-fire semantics clearly (SessionStart:startup vs SessionStart:resume vs in-process continuation)
  • Consider whether the /hooks slash command (or a new reload-hooks command) should explicitly re-trigger SessionStart hooks for testing

3. (Lower priority) if: filter on hook items appears unreliable across versions

I configured "if": "Bash(git push *)" on a PreToolUse hook entry per the documented schema. The filter was empirically ignored in a CC process that had started before the settings.json edit — the hook fired on every Bash command. After a full CLI restart, the filter began working correctly.

Most likely a stale-process artifact (settings.json read once at process start), not an actual filter bug. But it cost debugging time and led me to incorrectly suspect the if field was unsupported. Documenting that if: filter changes (and any other hook config changes) require CLI restart to take effect would prevent the same confusion for others.

---

Happy to share scripts, transcripts, or further repro detail if useful — just ask.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗