···11+# chook
22+33+A PreToolUse hook for Claude Code that gives you granular control over what Claude can do, with an interactive review TUI for building rules from real usage.
44+55+Zero dependencies. Single JS file. Works with any Claude Code session on your machine.
66+77+## Install
88+99+```bash
1010+ln -s /path/to/chook/chook.mjs /usr/local/bin/chook
1111+cp example.toml ~/.config/chook.toml
1212+```
1313+1414+Add to `~/.claude/settings.json`:
1515+1616+```json
1717+{
1818+ "hooks": {
1919+ "PreToolUse": [
2020+ {
2121+ "matcher": "*",
2222+ "hooks": [
2323+ {
2424+ "type": "command",
2525+ "command": "node /path/to/chook/chook.mjs run"
2626+ }
2727+ ]
2828+ }
2929+ ],
3030+ "PostToolUse": [
3131+ {
3232+ "matcher": "*",
3333+ "hooks": [
3434+ {
3535+ "type": "command",
3636+ "command": "node /path/to/chook/chook.mjs post"
3737+ }
3838+ ]
3939+ }
4040+ ]
4141+ }
4242+}
4343+```
4444+4545+The PostToolUse hook enables macOS notifications when Claude is waiting for your approval.
4646+4747+## How it works
4848+4949+Every time Claude tries to use a tool, the hook checks your rules:
5050+5151+1. **Deny rules** checked first — hard block, Claude sees your `reason` message
5252+2. **Allow rules** checked next — auto-approved, no prompt
5353+3. **No match** — passthrough to Claude Code's normal permission flow
5454+5555+```
5656+Claude tries tool → deny? → BLOCKED (with guidance)
5757+ → allow? → AUTO-APPROVED
5858+ → neither? → normal Claude Code prompt
5959+```
6060+6161+## Config
6262+6363+Default location: `~/.config/chook.toml` (override with `--config`)
6464+6565+### Deny rules
6666+6767+```toml
6868+[[deny]]
6969+tool = "Bash"
7070+command_regex = "^rm .*-rf"
7171+reason = "Use rm on specific files, or ask the user to run this manually."
7272+7373+[[deny]]
7474+tool = "Bash"
7575+command_regex = "&|;|\\||`|\\$\\("
7676+reason = "BLOCKED: You MUST run exactly ONE command per Bash call."
7777+7878+[[deny]]
7979+tool = "Read"
8080+file_path_regex = "\\.(env|secret|pem|key)$"
8181+reason = "Ask the user to provide the needed values."
8282+```
8383+8484+The `reason` string is shown to Claude, so write it as guidance for what to do instead.
8585+8686+### Allow rules
8787+8888+```toml
8989+# Auto-allow file ops in the project directory
9090+[[allow]]
9191+tool = "Read"
9292+restrict_to_cwd = true
9393+file_path_exclude_regex = "\\.\\."
9494+9595+# Auto-allow /tmp
9696+[[allow]]
9797+tool = "Read"
9898+file_path_regex = "^/tmp/"
9999+100100+# Auto-allow safe bash commands
101101+[[allow]]
102102+tool = "Bash"
103103+command_regex = "^git "
104104+command_exclude_regex = "push.*--force"
105105+106106+# Auto-allow specific subagents
107107+[[allow]]
108108+tool = "Task"
109109+subagent_type = "Explore"
110110+```
111111+112112+### Rule fields
113113+114114+| Field | Tools | Description |
115115+|---|---|---|
116116+| `tool` | all | Tool name to match (required) |
117117+| `reason` | all | Message shown to Claude on deny |
118118+| `restrict_to_cwd` | file tools | Restrict to the directory where Claude was started |
119119+| `file_path_regex` | Read, Write, Edit, Glob, NotebookEdit | Regex the file path must match |
120120+| `file_path_exclude_regex` | file tools | Regex that rejects the match |
121121+| `command_regex` | Bash | Regex the command must match |
122122+| `command_exclude_regex` | Bash | Regex that rejects the match |
123123+| `subagent_type` | Task | Exact match on subagent type |
124124+| `prompt_regex` | Task | Regex the prompt must match |
125125+| `prompt_exclude_regex` | Task | Regex that rejects the match |
126126+127127+### Auditing
128128+129129+```toml
130130+[audit]
131131+audit_file = "/tmp/chook-audit.json"
132132+audit_level = "matched" # off | matched | all
133133+```
134134+135135+## Commands
136136+137137+### `chook run`
138138+139139+Hook handler. Reads tool JSON from stdin, outputs permission decision to stdout. This is what Claude Code calls.
140140+141141+### `chook post`
142142+143143+PostToolUse handler. Cancels pending notifications when a tool completes.
144144+145145+### `chook validate`
146146+147147+Check your config for errors.
148148+149149+```
150150+$ chook validate
151151+Configuration is valid!
152152+ Deny rules: 5
153153+ Allow rules: 11
154154+ Audit file: /tmp/chook-audit.json
155155+ Audit level: matched
156156+```
157157+158158+### `chook review`
159159+160160+Interactive TUI for reviewing denied/passthrough tool uses and adding rules.
161161+162162+Entries are grouped into smart suggestions:
163163+- Bash commands grouped by command name (e.g. `git`, `npm`, `cargo`)
164164+- File operations grouped by project directory with `restrict_to_cwd`
165165+- Dangerous commands like `rm` get automatic `command_exclude_regex` for dangerous flags
166166+167167+Navigate with `j`/`k`, press `a` to allow, `d` to deny, `s` to skip, `q` to quit.
168168+169169+### `chook allow-last`
170170+171171+Add an allow rule from the most recent audit entry.
172172+173173+### `chook deny-last`
174174+175175+Add a deny rule from the most recent audit entry.
176176+177177+## Workflow
178178+179179+The fast feedback loop when Claude gets blocked:
180180+181181+1. Claude tries something, gets prompted or blocked
182182+2. `Ctrl-Z` to background Claude
183183+3. `chook allow-last` to allow it (or `chook review` to see all recent entries)
184184+4. `fg` to resume
185185+186186+Rules are live immediately — the config is read on every hook invocation.
187187+188188+## Notifications
189189+190190+When a tool passes through to Claude's normal permission flow and Claude is waiting for your approval, a macOS notification fires after 15 seconds. This way you'll notice if you're in another window. The PostToolUse hook cancels the notification if the tool completes quickly (meaning it was auto-approved by Claude's own settings, not actually waiting).
191191+192192+## Inspired by
193193+194194+[claude-code-permissions-hook](https://github.com/kornysietsma/claude-code-permissions-hook) by Korny Sietsma — a Rust implementation of the same idea. chook is a JS port with added interactive review and notifications.