Hook re-evaluation: UserPromptSubmit drops after mid-session script edits; Stop hook may miss first cold-start turn

Resolved 💬 1 comment Opened May 6, 2026 by deserin Closed Jun 3, 2026

Summary

Two related observations during a custom-hook install sequence on macOS, suggesting a possible incomplete merge / re-registration in Claude Code's hook lifecycle:

  1. Bug 1 (mid-session re-eval): When a UserPromptSubmit hook is installed via mid-session settings.json edit AND the hook script file is subsequently edited (e.g. for bug fixes), the UserPromptSubmit registration appears to drop intermittently. Stop hook on the same install does NOT drop. Workaround: restart Claude Code OR run /hooks to force a clean re-evaluation.
  1. Bug 2 (cold-session-start, possible): On a fresh session start, a Stop hook configured in settings.json may fail to fire on the very first assistant turn (or fires but is unable to read the transcript JSONL because it hasn't been flushed yet). Subsequent turns fire normally. Hypothesis: cold-start has a timing edge case (registration not fully active OR transcript file not yet available to the hook). Less certain than Bug 1; under instrumentation.

Environment

  • OS: macOS (Darwin 25.4.0)
  • Claude Code: 2.1.131
  • Settings file: project-scope .claude/settings.json (NOT global ~/.claude/settings.json)
  • Hook scripts: bash, executable, in .claude/hooks/*.sh

Reproduction (Bug 1 — mid-session UserPromptSubmit drop)

  1. In an active Claude session, register a UserPromptSubmit hook + a Stop hook in .claude/settings.json (single edit).
  2. Verify both hooks fire by triggering each event class.
  3. Edit the UserPromptSubmit hook script file (e.g. fix a bug in the bash script).
  4. Trigger UserPromptSubmit event again → hook does NOT fire.
  5. Trigger Stop event → Stop hook fires fine.
  6. Run /hooks → UserPromptSubmit re-registers; fires correctly thereafter.
  7. Edit the Stop hook script → Stop hook continues firing reliably.
  8. Edit the UserPromptSubmit script again → UserPromptSubmit drops again.

Pattern: UserPromptSubmit appears uniquely susceptible to script-Edit-induced re-evaluation drops. Stop hook is robust to the same edits.

Reproduction (Bug 2 — cold-start Stop hook miss)

Less reliable to reproduce; observed once in a fresh session. Instrumentation deployed in our environment to gather more evidence:

  1. Restart Claude Code completely (close + reopen).
  2. Begin a new session in a project with Stop hook registered in .claude/settings.json.
  3. Make assistant produce an output that the Stop hook script's logic SHOULD match (e.g. content-based pattern detection).
  4. Observed: Stop hook either does not fire OR fires with empty transcript_path content, missing the match.
  5. Subsequent turns fire correctly.

Hypothesis: Either (a) hook registration is not fully active on first turn, or (b) Stop hook fires before Claude Code has flushed the transcript JSONL file with the assistant's content. Manual replay of the regex against the eventually-written transcript content matches; the live fire missed.

Expected behavior

  • UserPromptSubmit registration should remain stable across hook script Edits (parity with Stop hook behavior).
  • Stop hook should reliably fire on the first turn of a cold session, with transcript_path pointing to a fully-flushed JSONL.

Actual behavior

  • UserPromptSubmit drops registration after script Edits; restart or /hooks reload required.
  • Stop hook miss observed once on cold-start turn 1; instrumentation pending to confirm.

Workarounds in use

  1. Run /hooks after any mid-session .claude/hooks/*.sh Edit to force clean re-evaluation.
  2. Avoid mid-session script edits during smoke-test windows; defer fixes to next session post-restart.
  3. For Stop hook content-based matching, accept that first-turn-of-session may miss; design mechanisms to be reactive across turns rather than dependent on single-turn fire.

Diagnostic data available

We have team_log rows with timestamps, hook fire/miss evidence, transcript paths, regex match replay results, and a 5-bug install timeline (Sessions 48 + 49 of an internal mechanism install). Happy to share sanitized excerpts if useful.

Suggested investigation paths

  1. Audit hook re-registration logic: when a .claude/hooks/*.sh file is Edited, what events does Claude Code emit? Does it differ between hook-event types?
  2. Audit Stop hook timing relative to transcript flush on cold-start: does the JSONL writer guarantee flush-before-Stop-fire?
  3. /hooks command invokes a code path that succeeds where script-Edit-triggered re-eval fails; comparing the two paths may localize the bug.

Related

May be related to closed issues touching hook merge/re-eval order — would appreciate a maintainer cross-reference if there's an open tracking issue.

---

Reporter context: Custom AskUserQuestion-default discipline mechanism install on macOS; hooks are a 2-hook bundle (Stop + UserPromptSubmit) for reactive discipline detection. Full timeline + evidence available on request.

View original on GitHub ↗

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