Hooks and rules I've built for my own Claude Code setup. Sharing them in case any are useful to you.
├── hooks/
│ └── check-command-antipatterns.sh # PreToolUse Bash hook that blocks diagnostic-output anti-patterns
└── rules/
├── claude-md-hygiene.md # What belongs in CLAUDE.md vs. issue tracker / MEMORY.md
├── command-output.md # Don't truncate diagnostic output (the rule enforced by the hook above)
├── conventional-commits.md # Conventional Commits + Angular flavor
├── gh-cli-github-fallback.md # Use `gh` CLI when WebFetch 404s on github.com
├── gh-watch.md # Prefer `gh run watch` / `gh pr checks --watch` over polling
├── rate-limit-external-apis.md # Default to 5–10s between requests to research APIs
├── secrets.md # Never put secret values in transcript-visible output
├── testing.md # Tests are a gate, not optional
└── verify-citations.md # Read the actual source before adding citations
A PreToolUse hook for the Bash tool. It catches four diagnostic-output
anti-patterns that are the single most common way Claude Code sessions
truncate the answer they were about to find:
| Anti-pattern | Carve-out (allowed) |
|---|---|
cmd | head ... / cmd | tail ... |
tail -f, tail -F, tail --follow (live log following) |
cmd | grep ... / ... | rg ... / ... | egrep ... / ... | ag ... |
none — even cat log | grep STR hides which subsystem the match came from. Direct grep file.log (no pipe) is fine — that's explicit search intent against a static target. |
cmd 2>/dev/null |
Optional-tool probes: command -v X 2>/dev/null, which X 2>/dev/null, X --version 2>/dev/null |
cmd || true / cmd || : |
Cleanup contexts: rm -f /tmp/..., kill %1, pkill X, teardown hooks |
When a command matches, the hook returns permissionDecision: "deny" and an
additionalContext string instructing Claude to surface three options to the
user via AskUserQuestion: run as-is, modify the command, or
cancel.
"Run as-is" is intentionally narrow: Claude writes a single-use token at
/tmp/claude-cmd-override-<sha16-of-cmd> and immediately re-runs the
same command. The hook detects the token (mtime within 60s), removes it,
and passes the command through. Because the token's filename is a SHA of the
exact command (whitespace-sensitive) and TTLs out after 60 seconds, it cannot
silently authorize subsequent similar commands.
┌──────────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ override token? │ → │ tripwire │ → │ LLM disambig. │ → │ JSON deny │
│ (single-use TTL) │ │ (substring) │ │ (claude -p) │ │ + ctx string │
└──────────────────┘ └──────────────┘ └─────────────────┘ └──────────────┘
↓ no match ↓ "OK"
pass through pass through
Every error path fails open. A broken hook must never brick bash — if
claude isn't on PATH, if jq errors, if the LLM times out, the command is
allowed through. The cost of a missed catch is much lower than the cost of
unrunnable bash.
The tripwire (a single grep -qE against the command string) eliminates
~99% of commands in microseconds without invoking the LLM, so the hook is
free in the common case.
# From a clone of this repo
cp hooks/check-command-antipatterns.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/check-command-antipatterns.sh
mkdir -p ~/.claude/rules
cp rules/*.md ~/.claude/rules/(Or symlink, or add this repo as a submodule of your own ~/.claude/ —
whatever your dotfiles setup prefers.)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/check-command-antipatterns.sh"
}
]
}
]
}
}The hook's deny message points Claude back to ~/.claude/rules/command-output.md
for context, so make sure that file is installed and (ideally) imported by your
CLAUDE.md so Claude internalizes the rule before the hook ever has to fire.
The hook shells out to:
bash(4+)jq— parsing thetool_inputJSON from the hook eventclaude(Claude Code CLI) — for the LLM disambiguation step. Fails open if unavailable, so the hook still degrades gracefully without it.shasum— hashing the command for the override token. Pre-installed on macOS; on Linux substitutesha256sumif needed.timeout(GNU coreutils) — bounds the LLM call to 12s. On macOS, install viabrew install coreutils(already present if you use Homebrew).
MIT. See LICENSE.