Set up the Claude Code permission hook that uses Haiku to auto-approve safe tool invocations based on a per-repo permission policy.
Authentication: The hook automatically reuses your Claude Code login (OAuth).
Steps
Step 1 — Check runtime availability
- Run
which bunto check if the Bun runtime is available. - If
bunis found, continue to Step 2. The hook command will be:bun .claude/skills/permission-policy/permission-policy.ts - If
bunis NOT found: a. Tell the user: "The permission-policy hook is written in TypeScript for the Bun runtime, butbunwas not found on your system." b. Ask the user which option they'd prefer:- Install Bun — They can install it from https://bun.sh and re-run
/permission-policy - Port to Node.js — You'll rewrite the hook as a standalone
.mjsscript (no dependencies) - Port to Python — You'll rewrite the hook as a standalone
.pyscript (no dependencies) - Port to another language — They can specify their preferred language/runtime c. If the user chooses to port:
- Read the original TypeScript source from
.claude/skills/permission-policy/permission-policy.ts - Rewrite it in the chosen language, preserving all behavior: stdin JSON parsing, permission policy loading,
claude -psubprocess call, JSON output, logging, and fail-open error handling - Write the ported script to
.claude/hooks/permission-policy.{ext}in the current project root (e.g..mjs,.py, etc.) - Make the file executable (
chmod +x) - Use this as the hook command for Step 4 instead of the default bun command (e.g.
node .claude/hooks/permission-policy.mjsorpython3 .claude/hooks/permission-policy.py) - Add
.claude/hooks/to.gitignore(in Step 3) d. If the user chooses to install Bun, stop here and tell them to re-run/permission-policyafter installing.
- Install Bun — They can install it from https://bun.sh and re-run
Step 2 — Create repo permission policy
- Check if
.claude/PERMISSION_POLICY.mdexists in the current project root. - If it already exists, tell the user: "Permission policy already exists at
.claude/PERMISSION_POLICY.md— skipping. Edit it to customize." - If it doesn't exist, copy the contents of
.claude/skills/permission-policy/PERMISSION_POLICY_TEMPLATE.mdto.claude/PERMISSION_POLICY.mdin the current project root. - Tell the user to customize the policy for their project.
Step 3 — Gitignore log file (and hooks if ported)
- Check if
.claude/logs/is already covered by a.gitignoreentry in the current project root (e.g..claude/logs/or.claude/logs). - If not, append
.claude/logs/on a new line to the project root.gitignore(create the file if it doesn't exist). - If the hook was ported in Step 1, also ensure
.claude/hooks/is in.gitignore. - Tell the user what was added to
.gitignore.
Step 4 — Configure hook in settings
- Read
.claude/settings.jsonin the current project root (or treat as{}if it doesn't exist). - Determine the hook command:
- If bun is available (default):
bun .claude/skills/permission-policy/permission-policy.ts - If ported in Step 1, use the appropriate command (e.g.
node .claude/hooks/permission-policy.mjsorpython3 .claude/hooks/permission-policy.py)
- If bun is available (default):
- Merge the following hook configuration, preserving all existing settings:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash|Read|Write|Edit|Glob|Grep|WebFetch|WebSearch",
"hooks": [
{
"type": "command",
"command": "<hook command from above>",
"timeout": 60
}
]
}
]
}
}
- If there's already a
PermissionRequesthook entry whose matcher includes"Bash", replace it. Otherwise, add a new entry. - Write the updated settings back.
Step 5 — Summary
- Tell the user about
.claude/PERMISSION_POLICY.md— explain that this is where they control what gets auto-approved, denied outright, or deferred to the human. Read the policy file and provide a brief summary of what it currently allows, denies, and asks about. - Tell the user about the log file at
.claude/logs/permission-policy.log. Encourage them to runtail -f .claude/logs/permission-policy.login a separate terminal if they want to follow the decisions being made in real time.