# hooks Claude Code hooks. Claude-only — Codex ignores. `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`. | Both fail open on anything they can't parse. Debug 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" }] } ], "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=$?" ```