permissions hook helper
JavaScript 100.0%
19 1 0

Clone this repository

https://tangled.org/burrito.space/chook https://tangled.org/did:plc:7r5c5jhtphcpkg3y55xu2y64/chook
git@tangled.org:burrito.space/chook git@tangled.org:did:plc:7r5c5jhtphcpkg3y55xu2y64/chook

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

chook#

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.

Zero dependencies. Single JS file. Works with any Claude Code session on your machine.

Install#

ln -s /path/to/chook/chook.mjs /usr/local/bin/chook
cp example.toml ~/.config/chook.toml

Add to ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node /path/to/chook/chook.mjs run"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node /path/to/chook/chook.mjs post"
          }
        ]
      }
    ]
  }
}

The PostToolUse hook enables macOS notifications when Claude is waiting for your approval.

How it works#

Every time Claude tries to use a tool, the hook checks your rules:

  1. Deny rules checked first — hard block, Claude sees your reason message
  2. Allow rules checked next — auto-approved, no prompt
  3. No match — passthrough to Claude Code's normal permission flow
Claude tries tool → deny? → BLOCKED (with guidance)
                  → allow? → AUTO-APPROVED
                  → neither? → normal Claude Code prompt

Config#

Default location: ~/.config/chook.toml (override with --config)

Deny rules#

[[deny]]
tool = "Bash"
command_regex = "^rm .*-rf"
reason = "Use rm on specific files, or ask the user to run this manually."

[[deny]]
tool = "Bash"
command_regex = "&|;|\\||`|\\$\\("
reason = "BLOCKED: You MUST run exactly ONE command per Bash call."

[[deny]]
tool = "Read"
file_path_regex = "\\.(env|secret|pem|key)$"
reason = "Ask the user to provide the needed values."

The reason string is shown to Claude, so write it as guidance for what to do instead.

Allow rules#

# Auto-allow file ops in the project directory
[[allow]]
tool = "Read"
restrict_to_cwd = true
file_path_exclude_regex = "\\.\\."

# Auto-allow /tmp
[[allow]]
tool = "Read"
file_path_regex = "^/tmp/"

# Auto-allow safe bash commands
[[allow]]
tool = "Bash"
command_regex = "^git "
command_exclude_regex = "push.*--force"

# Auto-allow specific subagents
[[allow]]
tool = "Task"
subagent_type = "Explore"

Rule fields#

Field Tools Description
tool all Tool name to match (required)
reason all Message shown to Claude on deny
restrict_to_cwd file tools Restrict to the directory where Claude was started
file_path_regex Read, Write, Edit, Glob, NotebookEdit Regex the file path must match
file_path_exclude_regex file tools Regex that rejects the match
command_regex Bash Regex the command must match
command_exclude_regex Bash Regex that rejects the match
subagent_type Task Exact match on subagent type
prompt_regex Task Regex the prompt must match
prompt_exclude_regex Task Regex that rejects the match

Auditing#

[audit]
audit_file = "/tmp/chook-audit.json"
audit_level = "matched"  # off | matched | all

Commands#

chook run#

Hook handler. Reads tool JSON from stdin, outputs permission decision to stdout. This is what Claude Code calls.

chook post#

PostToolUse handler. Cancels pending notifications when a tool completes.

chook validate#

Check your config for errors.

$ chook validate
Configuration is valid!
  Deny rules: 5
  Allow rules: 11
  Audit file: /tmp/chook-audit.json
  Audit level: matched

chook review#

Interactive TUI for reviewing denied/passthrough tool uses and adding rules.

Entries are grouped into smart suggestions:

  • Bash commands grouped by command name (e.g. git, npm, cargo)
  • File operations grouped by project directory with restrict_to_cwd
  • Dangerous commands like rm get automatic command_exclude_regex for dangerous flags

Navigate with j/k, press a to allow, d to deny, s to skip, q to quit.

chook allow-last#

Add an allow rule from the most recent audit entry.

chook deny-last#

Add a deny rule from the most recent audit entry.

Workflow#

The fast feedback loop when Claude gets blocked:

  1. Claude tries something, gets prompted or blocked
  2. Ctrl-Z to background Claude
  3. chook allow-last to allow it (or chook review to see all recent entries)
  4. fg to resume

Rules are live immediately — the config is read on every hook invocation.

Notifications#

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).

Inspired by#

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.