fix(hooks): pre-commit gate denies instead of ending the turn#2737
Conversation
The pre-commit build/lint/typecheck PreToolUse hook emitted {continue:false,
stopReason}, which halts the agent's turn — the dev had to type 'continue'
after every failed gate. Switch to a PreToolUse deny decision so the failing
commit is still blocked but the failure text is fed back to the agent as tool
feedback, letting it fix and retry without a manual nudge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
miguel-heygen
left a comment
There was a problem hiding this comment.
Clean, narrowly scoped hook fix at exact head 97ec7db.
.claude/settings.json:11now returns the documented PreToolUse deny payload (hookSpecificOutput.hookEventName,permissionDecision: "deny", andpermissionDecisionReason), so the commit remains blocked while the failure is returned as actionable tool feedback.- The surrounding command matcher and build/lint/typecheck gate are unchanged; the settings file parses as valid JSON.
- Required checks are pass/skipped as expected for this config-only change.
Verdict: APPROVE
Reasoning: The one-line semantic change matches the official hook contract and fixes the turn-ending behavior without weakening the commit gate.
— Magi
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Correct minimal fix. Switching the PreToolUse hook's failure output from { continue: false, stopReason } to { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason } } preserves the guard semantics (git commit still blocked) while returning the failure text to the agent as tool feedback instead of terminating the turn — matches Claude Code's hook contract for a PreToolUse permission decision.
Sanity-checked:
- JSON still parses; single hook in the file, no other
continue: falsesites to align. - Every non-output branch of the script (
cd <path>target resolution,git rev-parserepo detection,hyperframes-monoreponame gate, three build/lint/typecheck steps, 400-char failure truncation) is byte-identical — this is purely an output-format switch. - Wording nudge from "Fix the issues above before committing." → "Fix the issues above, then retry the commit." is right for the new shape: the message is now agent-facing feedback, not a user-facing stop reason.
LGTM from my side. Stamps sit with James.
miga-heygen
left a comment
There was a problem hiding this comment.
SSOT Review: pre-commit gate — deny instead of end-turn
HF #2737 (1 file, +1/-1)
One-line fix, correct behavior change:
Old: { continue: false, stopReason } — ends the agent's turn. The failure text never reaches the agent as actionable feedback; the dev has to manually type "continue" to resume.
New: { hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason } } — blocks the commit (same guard) but the failure text returns to the agent as tool feedback, so it can fix the issue and retry autonomously.
The gate logic (repo detection, build/lint/typecheck steps, failure formatting) is unchanged. Only the output shape changes.
Approve. This is the correct Claude Code hook semantics for "block this action but let the agent continue working."
--- Miga
Problem
The shared pre-commit hook in
.claude/settings.json(a PreToolUse:Bash gate that runs build/lint/typecheck before committing) emitted{ continue: false, stopReason }on failure.continue: falseends the agent's turn — so on every failed gate the dev had to manually type "continue" to resume, and the failure text never reached the agent as actionable feedback.Fix
Switch the failure branch to a PreToolUse deny decision (
hookSpecificOutput.permissionDecision: "deny"+permissionDecisionReason). Same guard — the failing commit is still blocked — but the failure text returns to the agent as tool feedback, so it fixes the issue and retries without a manual nudge.File is git-tracked and shared, so every dev inherits the fixed behavior on next pull.
🤖 Generated with Claude Code