fix: pin HOOK_SESSION_ID to stdin session_id so review gates compose - #105
Merged
Conversation
…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
marked this pull request as ready for review
April 24, 2026 22:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The review-gate hook chain (
review-gate-tracker.shonSubagentStop→review-gate-guard.shonPreToolUse) relied onHOOK_SESSION_IDto name per-session state files. WhenCLAUDE_SESSION_IDwas not exported to hook child processes (Claude Desktop on macOS doesn't),_lib.sh:19fell 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-gatesfiles and the guard always blocked03-code-quality-reviewerdispatch 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, andstate-cleanupfor the same reason — any hook pair that wrote in one process and read in another.Fix
Promote
.session_idfrom the hook input JSON (Claude Code always sends it on stdin) intoHOOK_SESSION_IDvia a newhook_init <input>helper in_lib.sh. Fall back to a stable per-worktreecurrentsentinel when neither$CLAUDE_SESSION_IDnor 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.shandplan-completion-guard.shnow 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.shexercises four scenarios withCLAUDE_SESSION_IDunset, each hook in its own process:session_id=sess-xyzon stdin writes tosess-xyz.review-gates.session_idin a different process reads that file back and allows03-code-quality-reviewer.session_idanywhere converge on thecurrentfallback and still compose.The test was confirmed to fail against the pre-patch
_lib.sh(tracker writes to<epoch>-<pid>.review-gatesinstead of the session id from stdin) and pass after the fix.Also smoke-tested
state-cleanup.shandplan-completion-guard.shend-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_lib.shstate-cleanup.shkeyed by stdinsession_idplan-completion-guard.shoverride lifecycle02-spec-reviewer→03-code-quality-reviewerwithoutCLAUDE_SESSION_IDexportedhttps://claude.ai/code/session_015BzhUP1i7uunxE6wcwWqiD
Generated by Claude Code