Skip to content

fix: pin HOOK_SESSION_ID to stdin session_id so review gates compose - #105

Merged
aram-devdocs merged 1 commit into
mainfrom
claude/lucid-davinci-XtfuF
Apr 24, 2026
Merged

fix: pin HOOK_SESSION_ID to stdin session_id so review gates compose#105
aram-devdocs merged 1 commit into
mainfrom
claude/lucid-davinci-XtfuF

Conversation

@aram-devdocs

Copy link
Copy Markdown
Owner

Summary

The review-gate hook chain (review-gate-tracker.sh on SubagentStopreview-gate-guard.sh on PreToolUse) relied on HOOK_SESSION_ID to name per-session state files. When CLAUDE_SESSION_ID was not exported to hook child processes (Claude Desktop on macOS doesn't), _lib.sh:19 fell back to $(date +%s)-$$ — a fresh id per hook invocation. The tracker and the guard ran in different bash processes, so they wrote and read different .review-gates files and the guard always blocked 03-code-quality-reviewer dispatch with "02-spec-reviewer has not returned APPROVE yet (current: none)" even after spec approved.

The same fallback silently broke role-marker / delegation-guard, delegation-tracker, save-session, plan-completion-guard, and state-cleanup for the same reason — any hook pair that wrote in one process and read in another.

Fix

Promote .session_id from the hook input JSON (Claude Code always sends it on stdin) into HOOK_SESSION_ID via a new hook_init <input> helper in _lib.sh. Fall back to a stable per-worktree current sentinel when neither $CLAUDE_SESSION_ID nor stdin carries it — matching the "only one review pipeline at a time per checkout" property.

Wire hook_init "$input" into every hook that touches session-scoped state. state-cleanup.sh and plan-completion-guard.sh now read stdin (they didn't before) so they can resolve the correct session id on Stop.

Verification

New bash test at .claude/hooks/tests/test_review_gate_session_id.sh exercises four scenarios with CLAUDE_SESSION_ID unset, each hook in its own process:

  • Tracker with session_id=sess-xyz on stdin writes to sess-xyz.review-gates.
  • Guard with the same stdin session_id in a different process reads that file back and allows 03-code-quality-reviewer.
  • Guard without a prior spec verdict still blocks.
  • Both hooks with no session_id anywhere converge on the current fallback and still compose.

The test was confirmed to fail against the pre-patch _lib.sh (tracker writes to <epoch>-<pid>.review-gates instead of the session id from stdin) and pass after the fix.

Also smoke-tested state-cleanup.sh and plan-completion-guard.sh end-to-end: cleanup now deletes the correct session's files; the override write/consume across two Stop invocations works.

Test plan

  • bash .claude/hooks/tests/test_review_gate_session_id.sh
  • Confirm regression test fails against buggy _lib.sh
  • Smoke test state-cleanup.sh keyed by stdin session_id
  • Smoke test plan-completion-guard.sh override lifecycle
  • End-to-end in a Claude Desktop session: 02-spec-reviewer03-code-quality-reviewer without CLAUDE_SESSION_ID exported

https://claude.ai/code/session_015BzhUP1i7uunxE6wcwWqiD


Generated by Claude Code

…ompose across hook invocations

The date+pid fallback in _lib.sh produced a fresh id per hook child
process on harnesses that do not export CLAUDE_SESSION_ID (e.g. Claude
Desktop on macOS). SubagentStop tracker and PreToolUse guard then wrote
and read different `.review-gates` files, so 03-code-quality-reviewer
dispatch was always blocked even after 02-spec-reviewer approved.

Promote `.session_id` from the hook input JSON (which Claude Code always
sends on stdin) into HOOK_SESSION_ID via a new `hook_init` helper, with
a stable per-worktree `current` sentinel as the last-resort fallback.
Wire the helper into every hook that touches session-scoped state —
role-marker, delegation-guard, delegation-tracker, review-gate-guard,
review-gate-tracker, save-session, plan-completion-guard, state-cleanup.

Add an end-to-end bash test that simulates two separate hook processes
with CLAUDE_SESSION_ID unset and asserts the gate pipeline composes:
tracker writes spec=APPROVE, guard reads it back, dispatch is allowed.
The test also covers the no-input fallback and the negative path (no
spec verdict → still blocks).

https://claude.ai/code/session_015BzhUP1i7uunxE6wcwWqiD
@aram-devdocs aram-devdocs changed the title fix(hooks): pin HOOK_SESSION_ID to stdin session_id so review gates compose fix: pin HOOK_SESSION_ID to stdin session_id so review gates compose Apr 24, 2026
@aram-devdocs
aram-devdocs marked this pull request as ready for review April 24, 2026 22:26
@aram-devdocs
aram-devdocs merged commit dd0cd18 into main Apr 24, 2026
14 of 16 checks passed
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.

2 participants