Files
agent-skills/hooks/README.md
T
Miguel PalhasandClaude Fable 5 3b9492f748
ci / nix (pull_request) Successful in 8s
ci / lint (pull_request) Successful in 10s
feat(hooks): daily warning for env values too short to guard
README now states the 6-char guarantee boundary instead of claiming
full coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 19:16:45 +01:00

44 lines
2.5 KiB
Markdown

# hooks
Claude Code hooks (`secret-guard.py` also serves Codex). `bin/link.sh` / `nix/home.nix` symlink these into `~/.claude/hooks/`; **wiring is manual**, see below.
| hook | event | what |
|------|-------|------|
| `comms-lint.py` | `PreToolUse` / `Bash` | Gates `gh issue\|pr create\|edit\|comment\|review`. Lints body against `claude-md/writing.md` (150-word target / 300 hard cap above fold, no reviewer-addressing opener, plain diction, ≤4 bold spans, no essay headings). Exit 2 blocks, stderr becomes feedback. |
| `comment-lint.py` | `PostToolUse` / `Write\|Edit\|MultiEdit` | Lints newly-added comment lines in code files against `claude-md/writing.md`. Exit 2 = revise nudge (edit already applied). Long-comment-run finding (>3 lines) is advisory, delivered via `additionalContext`. |
| `secret-guard.py` | `PreToolUse` / `Bash\|Write\|Edit\|MultiEdit\|NotebookEdit` | Blocks tool arguments carrying a live secret: any `~/.env.claude` value of 6+ chars (value-based, so near-zero false positives; 6-7 char values match as standalone tokens) plus literal token shapes (`ghp_`, `sk-`, `AKIA`, private-key headers, credential-bearing URLs). Values under 6 chars are too short to guard — the hook emits a daily rotate warning for them instead. Exit 2 blocks; stderr names the variable, never the value. Also serves Codex via the same entry in `~/.codex/hooks.json`. |
All fail open on anything they can't parse. Debug the linters with `COMMS_LINT_DEBUG=1` / `COMMENT_LINT_DEBUG=1`.
## Wiring
`~/.claude/settings.json` is machine-local (MCP servers, statusline, per-box hooks), so this repo does not own it. Merge into `.hooks`:
```json
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/comms-lint.py" }] },
{ "matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/secret-guard.py" }] }
],
"PostToolUse": [
{ "matcher": "Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/comment-lint.py" }] }
]
}
}
```
Contract prose lives in `claude-md/writing.md`, linked to `~/.claude/writing.md` and imported from `~/.claude/CLAUDE.md` via `@writing.md`. Both linters cite that path in their block message — moving the fragment means updating the linter string too.
## Testing a hook
Feed it the payload shape Claude Code sends:
```sh
echo '{"tool_name":"Bash","tool_input":{"command":"gh pr comment 1 --body \"short\""}}' \
| ~/.claude/hooks/comms-lint.py; echo "exit=$?"
```