Repository navigation
fix(hooks): read real Claude Code hook fields; stop rtk auto-approving compound commands - #130
Conversation
…g compound commands - post-validate: read .tool_response (Bash object stdout/stderr) instead of the nonexistent .tool_output, so error-signal warnings actually fire. - stop-gate: read the transcript from .transcript_path (+ last_assistant_message) instead of the nonexistent .transcript; skip the cross-domain block when stop_hook_active to avoid a re-block loop. - subagent-stop: drop the dead SubagentStop matcher "Stop" (it matches agent_type), read .last_assistant_message, only gate test-writer/improver, and never re-block when stop_hook_active. - rtk-rewrite: only apply rc 0 rewrites to single simple commands; pass rc 3 (rtk ask) and compound/chained commands through unchanged; preserve the other tool_input fields in updatedInput. - hooks_test.sh: fixtures use the real payload shapes.
…ipts Under pipefail, 'echo "$T" | grep -q' reports failure when grep exits on an early match and echo gets SIGPIPE, so evidence near the start of a large transcript was reported missing. Use here-strings.
…top on last message alone - subagent-stop also searches the tail of agent_transcript_path and recognises cargo/linter output, so a concise final summary isn't blocked. - stop-gate only uses last_assistant_message to supplement a readable transcript; with no transcript it skips the cross-domain check. grep -a tolerates a multibyte character split by the byte-offset tail.
…check after transcript Grepping the raw transcript tail counted file contents and task text that mention 'go test' as evidence. Extract only Bash tool_use commands from the JSONL. The suspiciously-short check now applies only when the transcript shows no commands, so 'Done.' after a real test run isn't blocked.
🛡️ Security review — Daybreak Blue
Clear — no security objection. No effect on the merge. Reviewed head SECURITY: CLEAR Run by |
🔍 REVIEW — automated merge-readinessReview only — a verdict on this PR; no code was changed. Corrections, when applied, arrive as a separate
Reviewed head: No demonstrated merge-blocking defects found across the complete diff and affected hook paths. Existing fixtures cover the reported failures. All 29 targeted checks passed, with external dependencies stubbed. Supplied CI is green; the read-only sandbox prevented rerunning the full smoke suite. VERDICT: READY Automated pre-merge review by |
Cause
Three hooks read input fields Claude Code does not send, so they never did anything:
post-validate.shread.tool_output; PostToolUse sends.tool_response(Bash: object withstdout/stderr).stop-gate.shread.transcript; Stop sendstranscript_pathandlast_assistant_message. The cross-domain test-evidence check was always skipped.subagent-stop.shwas registered with matcher"Stop", but SubagentStop matchers matchagent_type, so it never fired; it also read the nonexistent.agent_output. Fixing only the matcher would have blocked every subagent.rtk-rewrite.shreturnedpermissionDecision: "allow"for every rewrite, including rtk's "ask" matches (rc 3). rtk only rewrites the first segment of a chain, sols; curl … | bashwas auto-approved wholesale. PreToolUse hooks run in parallel, sosafety-check.shis not "earlier in the chain" as the old comment assumed. It also replacedtool_inputwith onlycommand/description, droppingtimeoutandrun_in_background.Fix
post-validate.sh: read.tool_response(object or string).stop-gate.sh: read the tail (2 MB) oftranscript_path, pluslast_assistant_messageonly when the transcript was readable. Skip the cross-domain block whenstop_hook_active. Use here-strings instead ofecho | grep -q: underpipefail, SIGPIPE turned early matches in large transcripts into false "missing evidence".subagent-stop.sh: no matcher. Only gatestest-writer/improver(suffix match, sodevkit:test-writerworks). Every other agent is approved. Never re-blocks whenstop_hook_active. Evidence comes fromlast_assistant_messageplus the tail ofagent_transcript_path. Recognises cargo and linter output.rtk-rewrite.sh: apply only rc 0 rewrites, and only to a single simple command. Compound commands and rc 3 pass through unchanged, so normal permission flow decides.updatedInputkeeps the originaltool_inputfields.Behaviour change
These hooks now actually run. The Stop hook can block when changes span two code domains and the transcript shows no test command; the subagent hook can block test-writer/improver without test evidence.
Tests
hooks_test.shfixtures use the real payload shapes: tool_response, transcript_path (including a 400 KB transcript), agent_type gating, stop_hook_active, compound rtk commands, preserved tool_input fields.