Block writes outside the repo
Stops Edit/Write calls whose target path resolves outside the project root.
PreToolUseEdit|Writeblocking riskneeds manual checkWhat it does
Resolves the real path of the write target (following symlinks, handling files that do not exist yet via their parent directory) and compares it with the real project root. Anything outside is denied. Catches ../ escapes and absolute paths to your home directory or /etc.
When it runs
| Event | PreToolUse |
| Matcher | Edit|Write |
| Scope | project |
| Platforms | macos, linux |
| Timeout | 10s |
| Requires | jq |
| Failure mode | block |
| Verified against | Claude Code hooks reference, event/matcher schema checked 2026-09-10 |
| Test status | needs manual check - run the test steps below before relying on it |
Config
Merge this into .claude/settings.json - or use the generator to combine hooks.
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-writes-outside-repo.sh",
"timeout": 10
}
],
"matcher": "Edit|Write"
}
]
}
}Script
Save as .claude/hooks/block-writes-outside-repo.sh and chmod +x it.
#!/bin/bash
# block-writes-outside-repo: deny writes that land outside the project root.
set -u
file=$(jq -r '.tool_input.file_path // empty')
[ -z "$file" ] && exit 0
root=$(cd "${CLAUDE_PROJECT_DIR:-$PWD}" && pwd -P)
# Resolve the target even if it does not exist yet: resolve the deepest
# existing ancestor, then append the rest.
target="$file"
case "$target" in /*) ;; *) target="$root/$target" ;; esac
dir=$(dirname "$target")
while [ ! -d "$dir" ] && [ "$dir" != "/" ]; do dir=$(dirname "$dir"); done
resolved_dir=$(cd "$dir" 2>/dev/null && pwd -P || echo "$dir")
resolved="$resolved_dir/$(basename "$target")"
case "$resolved" in
"$root"|"$root"/*) exit 0 ;;
esac
jq -n --arg f "$resolved" --arg r "$root" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ("Write outside project root blocked (" + $f + " is not under " + $r + ").")
}
}'
exit 0
Install
- Save the script to .claude/hooks/block-writes-outside-repo.sh and chmod +x it.
- Add the config fragment to .claude/settings.json.
Test it
- Positive: ask Claude to write to ~/notes.txt or ../outside.txt - both must be denied.
- Negative: writes inside the repo, including in new subdirectories, pass through.
- Symlink check: make a symlink inside the repo pointing outside it and have Claude write through it - it must be denied.
If it blocks something you wanted
If Claude legitimately needs a file outside the repo (a shared config, a sibling package), either move the work into the repo or allowlist that path in the script.
Sources
- Claude Code hooks reference (checked 2026-09-10)
Related hooks
Block destructive shell commands
Blocks rm -rf, disk tools, force pushes, and destructive git commands before they run.
PreToolUseBashblocking riskneeds manual checkProtect .env and secret files
Blocks Claude from writing to .env files, keys, and credential files - unless you say otherwise.
PreToolUseEdit|Writeblocking riskneeds manual checkPro Pack (later)
Framework bundles, cross-platform scripts, tested blocking policies, and a test harness. Free recipes and the generator stay free.
Waitlist opens soon.