The .claude/hooks/ gates have been running for a while now, so here is a field report: every hook firing from one long, productive session (2026-08-29) was checked against the current hook sources. The architecture held up well — fail-open, budget-bounded, portable, and two gates caught real problems. Six observations follow, each with a verdict and a concrete suggestion. They are suggestions only — the hooks are yours, @potatoqualitee.
| Hook |
Fired |
True positives |
Verdict |
stop-no-deflection.sh |
2× |
0 of 2 |
tune |
stop-todo-report.sh |
3× |
1 of 2 lines |
tune |
stop-verify.sh |
2× |
— |
fix bug |
validate-style.ps1 |
3× blocked |
0 of 3 |
tune |
pre-edit-read-check.sh |
1× |
by design |
leave |
| Remove-Item guard |
3× |
— |
not a repo hook |
stop-no-deflection.sh — tune
Fired twice, both false positives, no true positive all session. It flagged "separate PR" and "pre-existing" inside factually accurate reports — the words described real PR topology and real line ages, not blame-dodging. Each firing cost a full response rewrite.
The pattern list mixes two kinds of phrases. Some are genuinely evasive (not my responsibility, I did not introduce). Others are ordinary repo vocabulary: this project's policy is literally one PR per command, so "separate PR" appears in almost every honest status report, and "pre-existing" is standard triage language when a full-suite failure predates the branch.
Suggestion: drop the fact-shaped patterns — the pre-existing family, existed before, was already (broken|failing|there), already existed, not introduced by, and separate (refactor|effort|task|ticket|issue|PR) — and keep the evasive ones. Alternative with zero risk of losing catches: route it through emit_system_message (advisory) instead of a block.
stop-todo-report.sh — tune
Fired three times (the full stop-guard budget) on the same two lines. One was a true positive in spirit: a TODO: in a test file that correctly pushed a fixture rebuild up the priority list — that work then happened the same session. The other was the word "workaround" inside .EXAMPLE help prose about a SQL 2000 engine limitation — correct documentation, not a marker.
The root cause: the hook greps the entire changed file (line 22), not the lines the session added. dbatools deliberately keeps "workaround" comments documenting SMO defects — six of them recently became upstream issues — so any edit to such a file flags forever. The observed failure mode is worse than noise: prose has already been reworded (an XXX doc changed to XXXX) to dodge the grep, which is the repo adapting to the hook instead of the hook to the repo.
Suggestion: two independent tunes, either alone helps. (1) Scan only added lines — git diff -U0 filtered to ^+ — so pre-existing markers in touched files stay quiet. (2) Tighten the marker set: match case-sensitively (drop -i), require the colon form TODO:/FIXME:/HACK:, and drop WORKAROUND and XXX from the list — in this codebase they are documentation vocabulary, not unfinished-work markers.
stop-verify.sh — fix bug
Fired twice in one session although it says "fires ONCE per session". The checklist itself was useful both times — the content needs no change.
The mechanism is a real bug, and it is not in this file: the once-per-session marker (*_stop-verify.done) lives in the shared stop-guards state directory, and the janitor in lib-stop-guard.sh line 110 runs on every Stop event:
find "$_MARKER_DIR" -type f -mmin +60 -delete 2>/dev/null
That deletes every state file older than an hour — including the done-marker of a session that is still running. Any session longer than an hour gets the full quality gate again (and again each hour). The same janitor also wipes the advisory-mode markers and streak counters of long sessions, so other stop hooks quietly get fresh budgets too.
Suggestion: age the cleanup by session length, not by hook activity — simplest is -mmin +1440 (a day), or exclude *.done files from the sweep. The one-hour value predates day-long sessions.
validate-style.ps1 (via pre-write-style.sh) — tune
Blocked three legitimate edits, all on the single-quote rule; it also enforced correctly several times (alignment, quoting), so the rules themselves earn their keep — two regexes just can't see context.
1. Single quotes inside double-quoted strings. The rule (?<![@])'.+' (line 46) fires on T-SQL literals nested in double-quoted PowerShell — "… like 'dbatools%' …", "… WITH MARK 'dbatoolstest' …" — which is exactly the style the repo wants. Stripping double-quoted spans before the check fixes all three observed blocks:
$stripped = $line -replace '"[^"]*"', '""'
if ($stripped -match $patternSingleQuote) { ... }
2. Inline hashtables never leave the state machine. @{ anywhere on a line enters hashtable tracking, but the exit pattern ^\s*\} requires a line starting with a brace. A one-line @{ Name = 1 } therefore opens the tracker and never closes it, and every later line containing = is collected until some unrelated closing brace ends the block — producing false "misaligned hashtable" reports far from any hashtable. Skipping tracking when the @{ line closes its own brace fixes it.
Known limitation, probably fine: an Edit's new_string includes unchanged context lines, so a pre-existing violation next to the edit blocks it. Distinguishing context from new code inside the hook is not really possible; re-anchoring the edit works around it and the cost has been small.
Suggestion: apply the two regex fixes; leave the rule set as is.
pre-edit-read-check.sh — leave
Blocked one edit after a context compaction, although the file had been Read earlier. Checked against source: this is designed behavior, not a reset bug — session-compact-reset-reads.sh clears the Read tracker on compact|resume precisely because the agent's memory of what it read is gone after compaction. One extra Read is the intended price. No change needed.
Remove-Item guard ("system path '*' is blocked") — not a repo hook
Blocked Remove-Item three times: twice on variable paths it could not resolve, once on a literal UNC path. Traced: this message exists nowhere in .claude/ — pre-bash-guard.sh only blocks rm on filesystem roots. The blocks come from Claude Code's own sandbox layer, so there is nothing here to tune; listed only so the inventory is complete.
What worked
pre-bash-commit-do.sh and stop-registration-check.sh fired only when correct all session.
- The stop-guard budget did its job: no gate ever trapped the session, and the worst case was three rewrites.
- The earlier infinite-loop problem was environmental (no JSON parser in Git Bash) and is fixed —
jq is installed and hooks-doctor.sh reports clean.
Sample: one session, 2026-08-29, on Windows / Git Bash hooks; sources verified 2026-08-30.
This report was created by Claude and reviewed by Andreas Jordan.
The
.claude/hooks/gates have been running for a while now, so here is a field report: every hook firing from one long, productive session (2026-08-29) was checked against the current hook sources. The architecture held up well — fail-open, budget-bounded, portable, and two gates caught real problems. Six observations follow, each with a verdict and a concrete suggestion. They are suggestions only — the hooks are yours, @potatoqualitee.stop-no-deflection.shstop-todo-report.shstop-verify.shvalidate-style.ps1pre-edit-read-check.shstop-no-deflection.sh — tune
Fired twice, both false positives, no true positive all session. It flagged "separate PR" and "pre-existing" inside factually accurate reports — the words described real PR topology and real line ages, not blame-dodging. Each firing cost a full response rewrite.
The pattern list mixes two kinds of phrases. Some are genuinely evasive (
not my responsibility,I did not introduce). Others are ordinary repo vocabulary: this project's policy is literally one PR per command, so "separate PR" appears in almost every honest status report, and "pre-existing" is standard triage language when a full-suite failure predates the branch.Suggestion: drop the fact-shaped patterns — the
pre-existingfamily,existed before,was already (broken|failing|there),already existed,not introduced by, andseparate (refactor|effort|task|ticket|issue|PR)— and keep the evasive ones. Alternative with zero risk of losing catches: route it throughemit_system_message(advisory) instead of a block.stop-todo-report.sh — tune
Fired three times (the full stop-guard budget) on the same two lines. One was a true positive in spirit: a
TODO:in a test file that correctly pushed a fixture rebuild up the priority list — that work then happened the same session. The other was the word "workaround" inside.EXAMPLEhelp prose about a SQL 2000 engine limitation — correct documentation, not a marker.The root cause: the hook greps the entire changed file (line 22), not the lines the session added. dbatools deliberately keeps "workaround" comments documenting SMO defects — six of them recently became upstream issues — so any edit to such a file flags forever. The observed failure mode is worse than noise: prose has already been reworded (an
XXXdoc changed toXXXX) to dodge the grep, which is the repo adapting to the hook instead of the hook to the repo.Suggestion: two independent tunes, either alone helps. (1) Scan only added lines —
git diff -U0filtered to^+— so pre-existing markers in touched files stay quiet. (2) Tighten the marker set: match case-sensitively (drop-i), require the colon formTODO:/FIXME:/HACK:, and dropWORKAROUNDandXXXfrom the list — in this codebase they are documentation vocabulary, not unfinished-work markers.stop-verify.sh — fix bug
Fired twice in one session although it says "fires ONCE per session". The checklist itself was useful both times — the content needs no change.
The mechanism is a real bug, and it is not in this file: the once-per-session marker (
*_stop-verify.done) lives in the sharedstop-guardsstate directory, and the janitor inlib-stop-guard.shline 110 runs on every Stop event:That deletes every state file older than an hour — including the done-marker of a session that is still running. Any session longer than an hour gets the full quality gate again (and again each hour). The same janitor also wipes the advisory-mode markers and streak counters of long sessions, so other stop hooks quietly get fresh budgets too.
Suggestion: age the cleanup by session length, not by hook activity — simplest is
-mmin +1440(a day), or exclude*.donefiles from the sweep. The one-hour value predates day-long sessions.validate-style.ps1 (via pre-write-style.sh) — tune
Blocked three legitimate edits, all on the single-quote rule; it also enforced correctly several times (alignment, quoting), so the rules themselves earn their keep — two regexes just can't see context.
1. Single quotes inside double-quoted strings. The rule
(?<![@])'.+'(line 46) fires on T-SQL literals nested in double-quoted PowerShell —"… like 'dbatools%' …","… WITH MARK 'dbatoolstest' …"— which is exactly the style the repo wants. Stripping double-quoted spans before the check fixes all three observed blocks:2. Inline hashtables never leave the state machine.
@{anywhere on a line enters hashtable tracking, but the exit pattern^\s*\}requires a line starting with a brace. A one-line@{ Name = 1 }therefore opens the tracker and never closes it, and every later line containing=is collected until some unrelated closing brace ends the block — producing false "misaligned hashtable" reports far from any hashtable. Skipping tracking when the@{line closes its own brace fixes it.Known limitation, probably fine: an Edit's
new_stringincludes unchanged context lines, so a pre-existing violation next to the edit blocks it. Distinguishing context from new code inside the hook is not really possible; re-anchoring the edit works around it and the cost has been small.Suggestion: apply the two regex fixes; leave the rule set as is.
pre-edit-read-check.sh — leave
Blocked one edit after a context compaction, although the file had been Read earlier. Checked against source: this is designed behavior, not a reset bug —
session-compact-reset-reads.shclears the Read tracker oncompact|resumeprecisely because the agent's memory of what it read is gone after compaction. One extra Read is the intended price. No change needed.Remove-Item guard ("system path '*' is blocked") — not a repo hook
Blocked
Remove-Itemthree times: twice on variable paths it could not resolve, once on a literal UNC path. Traced: this message exists nowhere in.claude/—pre-bash-guard.shonly blocksrmon filesystem roots. The blocks come from Claude Code's own sandbox layer, so there is nothing here to tune; listed only so the inventory is complete.What worked
pre-bash-commit-do.shandstop-registration-check.shfired only when correct all session.jqis installed andhooks-doctor.shreports clean.Sample: one session, 2026-08-29, on Windows / Git Bash hooks; sources verified 2026-08-30.
This report was created by Claude and reviewed by Andreas Jordan.