permissions hook helper
1# chook
2
3A 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.
4
5Zero dependencies. Single JS file. Works with any Claude Code session on your machine.
6
7## Install
8
9```bash
10ln -s /path/to/chook/chook.mjs /usr/local/bin/chook
11cp example.toml ~/.config/chook.toml
12```
13
14Add to `~/.claude/settings.json`:
15
16```json
17{
18 "hooks": {
19 "PreToolUse": [
20 {
21 "matcher": "*",
22 "hooks": [
23 {
24 "type": "command",
25 "command": "node /path/to/chook/chook.mjs run"
26 }
27 ]
28 }
29 ],
30 "PostToolUse": [
31 {
32 "matcher": "*",
33 "hooks": [
34 {
35 "type": "command",
36 "command": "node /path/to/chook/chook.mjs post"
37 }
38 ]
39 }
40 ]
41 }
42}
43```
44
45The PostToolUse hook enables macOS notifications when Claude is waiting for your approval.
46
47## How it works
48
49Every time Claude tries to use a tool, the hook checks your rules:
50
511. **Deny rules** checked first — hard block, Claude sees your `reason` message
522. **Allow rules** checked next — auto-approved, no prompt
533. **No match** — passthrough to Claude Code's normal permission flow
54
55```
56Claude tries tool → deny? → BLOCKED (with guidance)
57 → allow? → AUTO-APPROVED
58 → neither? → normal Claude Code prompt
59```
60
61## Config
62
63Default location: `~/.config/chook.toml` (override with `--config`)
64
65### Deny rules
66
67```toml
68[[deny]]
69tool = "Bash"
70command_regex = "^rm .*-rf"
71reason = "Use rm on specific files, or ask the user to run this manually."
72
73[[deny]]
74tool = "Bash"
75command_regex = "&|;|\\||`|\\$\\("
76reason = "BLOCKED: You MUST run exactly ONE command per Bash call."
77
78[[deny]]
79tool = "Read"
80file_path_regex = "\\.(env|secret|pem|key)$"
81reason = "Ask the user to provide the needed values."
82```
83
84The `reason` string is shown to Claude, so write it as guidance for what to do instead.
85
86### Allow rules
87
88```toml
89# Auto-allow file ops in the project directory
90[[allow]]
91tool = "Read"
92restrict_to_cwd = true
93file_path_exclude_regex = "\\.\\."
94
95# Auto-allow /tmp
96[[allow]]
97tool = "Read"
98file_path_regex = "^/tmp/"
99
100# Auto-allow safe bash commands
101[[allow]]
102tool = "Bash"
103command_regex = "^git "
104command_exclude_regex = "push.*--force"
105
106# Auto-allow specific subagents
107[[allow]]
108tool = "Task"
109subagent_type = "Explore"
110```
111
112### Rule fields
113
114| Field | Tools | Description |
115|---|---|---|
116| `tool` | all | Tool name to match (required) |
117| `reason` | all | Message shown to Claude on deny |
118| `restrict_to_cwd` | file tools | Restrict to the directory where Claude was started |
119| `file_path_regex` | Read, Write, Edit, Glob, NotebookEdit | Regex the file path must match |
120| `file_path_exclude_regex` | file tools | Regex that rejects the match |
121| `command_regex` | Bash | Regex the command must match |
122| `command_exclude_regex` | Bash | Regex that rejects the match |
123| `subagent_type` | Task | Exact match on subagent type |
124| `prompt_regex` | Task | Regex the prompt must match |
125| `prompt_exclude_regex` | Task | Regex that rejects the match |
126
127### Auditing
128
129```toml
130[audit]
131audit_file = "/tmp/chook-audit.json"
132audit_level = "matched" # off | matched | all
133```
134
135## Commands
136
137### `chook run`
138
139Hook handler. Reads tool JSON from stdin, outputs permission decision to stdout. This is what Claude Code calls.
140
141### `chook post`
142
143PostToolUse handler. Cancels pending notifications when a tool completes.
144
145### `chook validate`
146
147Check your config for errors.
148
149```
150$ chook validate
151Configuration is valid!
152 Deny rules: 5
153 Allow rules: 11
154 Audit file: /tmp/chook-audit.json
155 Audit level: matched
156```
157
158### `chook review`
159
160Interactive TUI for reviewing denied/passthrough tool uses and adding rules.
161
162Entries are grouped into smart suggestions:
163- Bash commands grouped by command name (e.g. `git`, `npm`, `cargo`)
164- File operations grouped by project directory with `restrict_to_cwd`
165- Dangerous commands like `rm` get automatic `command_exclude_regex` for dangerous flags
166
167Navigate with `j`/`k`, press `a` to allow, `d` to deny, `s` to skip, `q` to quit.
168
169### `chook allow-last`
170
171Add an allow rule from the most recent audit entry.
172
173### `chook deny-last`
174
175Add a deny rule from the most recent audit entry.
176
177## Workflow
178
179The fast feedback loop when Claude gets blocked:
180
1811. Claude tries something, gets prompted or blocked
1822. `Ctrl-Z` to background Claude
1833. `chook allow-last` to allow it (or `chook review` to see all recent entries)
1844. `fg` to resume
185
186Rules are live immediately — the config is read on every hook invocation.
187
188## Notifications
189
190When 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).
191
192## Inspired by
193
194[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.