Files
ersatztv/.claude/settings.json
T
139bdaff5a chore: PreToolUse guard that blocks a commit/push with a BOM'd touched .cs
The #311 fix-as-you-touch rule is already enforced by a pre-commit hook and a
blocking CI job, and both missed the same mistake twice on 2026-07-17: PR #405
shipped 6 BOM'd files, PR #402 shipped 19. The gap is that the pre-commit hook is
routinely skipped — worktree hook friction makes `git commit --no-verify` the
normal path here — which leaves CI, a ~10-minute round trip, as the first thing
that tells you.

This guard fires on Bash before git runs, so --no-verify can't skip it. It denies
`git commit`/`git push` when a .cs the branch touches still carries a BOM, names
the files, and gives the strip command.

Why a hook rather than a note: a memory describing this exact trap did not prevent
either failure — the PR #402 session re-added a BOM an hour after writing that
memory down, because the usual ways of touching a legacy file re-add it silently
(Python io.open(..., encoding='utf-8-sig') WRITES a BOM back; sed/perl round-trips
keep it). A check that runs beats one you have to remember.

Follows the existing gate pattern: a fail-open script in .claude/hooks/ registered
on the PreToolUse/Bash matcher alongside bash-guard and worktree-guard. Any parse
or lookup trouble → allow; this must never be the reason a commit can't happen, and
CI remains the backstop. Generated *.Designer.cs / TvContextModelSnapshot.cs are
exempt, matching what dotnet format itself skips. Scoped to this repo, and it
resolves the target tree from an in-command `cd` because commits here run as
`cd <worktree> && git ...` and the harness resets the shell cwd between calls.

Verified against a throwaway fixture: denies commit and push for a BOM'd touched
file (naming it); allows a clean tree, a BOM in generated files only, `git status`
with a BOM present, and `echo 'run git push later'` (no false-trip on the words).
Proved live via a sentinel — it fires on the Bash matcher — sentinel removed.

docs/contributing.md updated: three enforcement layers, why the third exists, the
utf-8-sig re-add trap, a by-hand check, and the bash-not-zsh caveat for
`dotnet format --include` (mapfile is bash-only; under zsh the file list is empty
and the tool looks like it silently did nothing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:50:01 +00:00

91 lines
2.3 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-bash-guard.sh\"",
"timeout": 10
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-worktree-guard.sh\"",
"timeout": 10
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-bom-guard.sh\"",
"timeout": 10
}
]
},
{
"matcher": "mcp__plugin_playwright_playwright__browser_navigate|mcp__claude-in-chrome__navigate",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-nav-guard.sh\"",
"timeout": 10
}
]
},
{
"matcher": "Agent|Task",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-agent-ram.sh\"",
"timeout": 10
}
]
},
{
"matcher": "mcp__gitea__pull_request_write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pretooluse-merge-consent.sh\"",
"timeout": 15
}
]
},
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/design-sync-reminder.sh\" start",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/posttooluse-worktree-marker.sh\"",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/design-sync-reminder.sh\" finish",
"timeout": 10
}
]
}
]
}
}