How It Works
Built-in rules
safe-chains knows 453 commands. For each one it validates specific subcommands and flags, allowing git log but not git push, allowing sed 's/foo/bar/' but not sed -i.
Parsing example
Take this command from the introduction:
find src -name "*.rs" -exec grep -l "TODO" {} \; | sort | while read f; do echo "=== $f ==="; grep -n "TODO" "$f"; done
safe-chains parses this and validates every leaf:
- Pipeline segment 1:
find src -name "*.rs" -exec grep -l "TODO" {} \;findis allowed with positional predicates-exectriggers delegation: the inner commandgrep -l "TODO" {}is extracted and validated separatelygrep -lpasses (-lis an allowed flag)
- Pipeline segment 2:
sortpasses (safe with any arguments) - Pipeline segment 3:
while read f; do ...; doneis a compound command, parsed recursively:read fpasses (shell builtin)echo "=== $f ==="passesgrep -n "TODO" "$f"passes (-nis an allowed flag)
Every leaf is safe, so the entire command is approved.
Compound commands
Shell compound commands (for/while/until loops and if/elif/else conditionals) are parsed and each leaf command is validated recursively, supporting arbitrary nesting depth.
Output redirection (>, >>) to /dev/null is inert. Output redirection to other files is allowed at safe-write level. Input redirection (<), here-strings (<<<), and here-documents (<<, <<-) are allowed.
Backticks and command substitution ($(...)) are recursively validated.
Interaction with approved commands
safe-chains runs as a pre-hook. If it approves, Claude Code skips the prompt. If it doesn’t recognize the command, Claude Code’s normal permission flow takes over (checking your Bash(...) patterns in settings, or prompting).
Where this gets interesting is chained commands. Claude Code matches approved patterns against the full command string. If you approved Bash(cargo test:*) and Claude runs cargo test && ./generate-docs.sh, Claude Code won’t match — the full string isn’t just cargo test.
safe-chains splits the chain and checks each segment independently. cargo test passes built-in rules. ./generate-docs.sh matches Bash(./generate-docs.sh:*) from your settings. Both segments covered, chain auto-approved.
Once safe-chains is handling your safe commands, most of your existing approved patterns are redundant. Strip them down to project-specific scripts and tools safe-chains doesn’t know about. See Cleaning up approved commands.
For example, given cargo test && npm run build && ./generate-docs.sh:
cargo testpasses built-in rulesnpm run buildmatchesBash(npm run:*)from settings./generate-docs.shmatchesBash(./generate-docs.sh:*)from settings