Guide
Automating Claude Code safely: pre-approval policies that actually work
You want Claude Code to run autonomously — hand it a task, walk away, come back to a PR or a commit. But
"autonomous" and "safe" feel like they're in tension. Let the agent run unchecked and it might blast
rm -rf across your entire codebase. Require approval for every action and you're babysitting
it again.
The middle ground is pre-approval policies. You define rules up front — "allow git status
everywhere," "block rm outside this repo," "ask me before git push" — and Claude
Code checks each tool call against those rules before running it. No decisions mid-task, no prompts you'll
miss. The agent runs with the freedom you've pre-granted.
How pre-approval policies work
Policies live in Claude Code settings via the PreToolUse hook. When the agent is about to run
a tool — a shell command, a file edit, a fetch — Claude Code fires the hook with the command and its
context. Your hook script reads the JSON, evaluates it against your rules, and decides: allow, block, or
escalate it back to you for approval.
The beauty is that this happens instantly and silently. No toast, no pause, no "allow this?" prompt unless the policy says "I don't know." The agent runs what you've already blessed.
A minimal policy is a shell script that reads the tool name and working directory from the JSON payload and pattern-matches against a whitelist or blacklist:
#!/usr/bin/env bash
payload="$(cat)"
tool="$(echo "$payload" | jq -r '.tool_name')"
command="$(echo "$payload" | jq -r '.command // .content')"
# Block all rm commands
if [[ "$tool" == "bash" && "$command" =~ rm ]]; then
exit 1
fi
# Allow everything else
exit 0 That's a deny-by-exception pattern: block the dangerous stuff explicitly, allow everything else. You can flip it (allow-by-exception: block everything by default, allow only safe commands), but for most teams, deny-by-exception is easier to maintain and less likely to grind the agent to a halt.
The safest policies to start with
You don't need to be exhaustive. A few rules catch most of the risky patterns. Start here and add as your team learns what matters:
- Block
rm -rfglobally, or only allow it in specific directories. A runaway agent with recursive delete is your worst-case scenario. Require an explicit path and refuse to touch anything above the repo root. - Block access to sensitive paths. Anything under
~/.ssh,~/.aws, or your secrets manager should be off-limits. The agent doesn't need those to code. - Require approval for
git push,npm publish, and other release commands. These are the "no undo" operations. Let the agent do the work, but escalate the final publish to you. - Allow read-only commands without gates.
git status,ls,cat,grep— these can't hurt anything. Let them run freely. - Block commands outside the working directory. If the agent is supposed to work in
/home/user/myapp, don't let it poke around in/or your home directory. Compare the working directory to a whitelist of allowed paths.
Building your policy script
A real policy for a team is usually 50–100 lines of bash (or Python, or whatever). Here's a skeleton that combines a few of the patterns above:
#!/usr/bin/env bash
payload="$(cat)"
tool="$(echo "$payload" | jq -r '.tool_name')"
command="$(echo "$payload" | jq -r '.command // .content // empty')"
cwd="$(echo "$payload" | jq -r '.working_directory')"
# Blocklist: deny these unconditionally
if [[ "$tool" == "bash" || "$tool" == "shell" ]]; then
if [[ "$command" =~ rm\ -rf|\.ssh|\.aws ]]; then
exit 1
fi
fi
# Escalation list: ask before running
if [[ "$command" =~ git\ push|npm\ publish ]]; then
exit 2
fi
# Whitelist: allow read-only operations without hesitation
if [[ "$command" =~ ^(git\ status|git\ log|ls|cat|grep) ]]; then
exit 0
fi
# Default: allow, with optional logging
echo "Allowing: $command in $cwd" >> /tmp/claude-policy.log
exit 0
Save that as a script, make it executable, and wire it into your settings under the PreToolUse
hook. The logic is dead simple: scan the command, compare it to your rules, return a decision.
How to hook it into Claude Code
Add a hooks section to your Claude Code settings (if it doesn't exist) and register your
policy under PreToolUse:
{
"hooks": {
"PreToolUse": "~/.config/claude-code/policy.sh"
}
}
That's it. From now on, every tool call goes through your policy before running. For more on hooks in general — how to parse the JSON, the events available, and decision semantics — see our guide to Claude Code hooks.
Policy patterns for different team scenarios
The policy above is a good general start, but different teams need different defaults. Here are the patterns that work:
- Solo developer, high trust: Allow most things, block only
rm -rfand SSH access. You're trying to stay out of the agent's way, not micromanage it. - Team in a monorepo: Allow everything inside
/repo, deny everything outside. Add escalation forgit pushand dependency updates. The boundary is clear. - Production CI/CD: Deny by default, allow only a curated set (git status, npm test, npm run build). Escalate anything risky. This is the most restrictive, but safe for automated deployments.
- Data team or security-sensitive work: Deny access to any secrets files, databases, or network calls outside a whitelist. Escalate everything that touches those.
When escalation saves you
The "escalate" outcome is underrated. It's the "I don't know, ask the human" response. The agent pauses, a notification fires (via the Notification hook), and you get prompted. This is perfect for the gray zone — operations that are usually safe but have enough risk that the decision should be conscious.
git push is the classic example. You don't want to block it (the agent earned the right to
ship), but you also don't want it running silently at 2 a.m. Escalation is the sweet spot: the agent does
the work, then pings you with "ready to push, allow?" and you're back in the loop for the moment that
matters.
Iterating on your policies
You'll refine these policies as you learn what breaks things. A good workflow is:
- Start restrictive (deny most things, allow only safe operations).
- Watch what the agent tries to do and what gets blocked.
- Adjust: move blocked-but-safe stuff into the allow list, escalate the gray-area stuff.
- Log every decision (block, allow, escalate) so you can tune over time.
The policy script above logs to /tmp/claude-policy.log. Tail that while you work, and in a
week you'll have a clear picture of what your agent actually needs and what you can keep locked down.
Where Backgrind fits
Policies handle the decisions you can make ahead of time. Backgrind handles the ones you can't: when a tool call escalates — or the agent just needs a yes/no — Backgrind catches that PreToolUse / Notification event and surfaces it as an ambient prompt right in its always-on-top overlay. You approve or deny on the spot, instead of hunting for a buried terminal. The agent runs with the freedom your policy pre-granted, and the moments that genuinely need you come to you.
Pair that with running Claude Code in the background and you get the full picture: autonomous work behind a pre-approval policy, a chime only when something needs a decision, and no babysitting. See it in the live demo.