By oryband
Securely run compound Bash commands like pipes, chains, and subshells without manual approval for each segment: parses via shfmt AST, checks against allow/deny lists, enforces bash safeguards, and blocks unsafe git branch changes via pre-tool hooks.
npx claudepluginhub oryband/claude-code-auto-approve --plugin auto-approve-compound-bashAuto-approve compound Bash commands (pipes, chains, subshells) by parsing each segment via shfmt AST and checking against allow/deny lists
PreToolUse hook that auto-approves safe Bash commands by parsing them into an AST and matching against configurable patterns
Smart command safety filter for Claude Code — parses shell pipelines and evaluates per-command safety rules to auto-approve safe commands and block dangerous ones
Block destructive git and filesystem commands before execution
Safety hooks to block or require user approval for dangerous commands (rm, git operations, .env access, file size limits)
Ultra-compressed communication mode. Cuts ~75% of tokens while keeping full technical accuracy by speaking like a caveman.
Executes bash commands
Hook triggers when Bash tool is used
Share bugs, ideas, or general feedback.
A Claude Code hook that auto-approves compound Bash commands when every sub-command is in your allow list and none are in your deny list.
Claude Code matches Bash(cmd *) permissions against the full command string. ls | grep foo doesn't match Bash(ls *) or Bash(grep *), so you get prompted even though both commands are individually allowed. Same for nvm use && yarn test, git log | head, mkdir -p dir && cd dir, etc.
This hook parses compound commands into segments and checks each one.
Requires bash 4.3+ (auto-detected; re-execs with Homebrew bash on macOS if needed), shfmt, and jq.
brew install shfmt jq
Copy the script somewhere and register it in ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "~/.claude/scripts/approve-compound-bash.sh",
"timeout": 3
}]
}]
},
"permissions": {
"allow": [
"Bash(ls *)", "Bash(grep *)", "Bash(git *)" // ...
],
"deny": [
"Bash(git push --force *)", "Bash(rm -rf / *)" // ...
]
}
}
The hook reads permissions from all settings layers (global, global local, project, project local), supports all permission formats (Bash(cmd *), Bash(cmd:*), Bash(cmd)), and strips env var prefixes (NODE_ENV=prod npm test matches npm).
Simple commands (no |, &, ;, `, $() are checked directly against your prefix lists. No parsing overhead.
Compound commands are parsed into a JSON AST by shfmt, walked by a jq filter that extracts every sub-command (including inside $(...), <(...), subshells, if/for/while/case bodies, bash -c arguments, etc.), then each segment is checked.
Three outcomes:
On any error the hook falls through. It never approves something it can't fully analyze.
Extract sub-commands from a compound command:
echo 'nvm use && yarn test' | ./approve-compound-bash.sh parse
# nvm use
# yarn test
Verbose mode shows matching decisions on stderr:
echo '{"tool_input":{"command":"ls | grep foo"}}' | ./approve-compound-bash.sh --debug
97 tests across parsing, permissions, and security. Requires BATS.
bats test/
bash -c on simple path: bash -c 'echo hello' has no shell metacharacters, so it takes the fast path and matches against the prefix list as-is without recursing into the inner command. Don't add bash, sh, or zsh to your allow list.
Why this hook exists. Claude Code evaluates Bash(cmd *) permissions against the full command string. Compound commands like ls | grep foo or nvm use && yarn test don't match individual prefix rules, so users get prompted even when every sub-command is already allowed. As of March 2026, this remains an open issue with no native fix.
Why bash + shfmt + jq. Claude Code plugins are expected to be transparent and auditable — compiled binaries and obfuscated code are explicitly discouraged. A bash script with well-known dependencies meets this standard. shfmt and jq are both small, fast, and available via standard package managers.
Why shfmt for parsing. shfmt (mvdan.cc/sh) is the most complete and battle-tested bash parser available. Its JSON AST output covers all compound constructs: pipes, chains, subshells, command/process substitution, control flow, and declarations. Alternatives like tree-sitter-bash are designed for editor highlighting rather than semantic analysis, and hand-written parsers (as used by Dippy) trade external dependencies for ongoing maintenance burden and potential correctness gaps.
Why not a compiled binary. A Go rewrite using mvdan.cc/sh as a library would eliminate the shfmt and jq subprocesses, but would produce an opaque binary that conflicts with the plugin ecosystem's source-readability expectations. The current approach adds ~100–150ms of subprocess overhead per compound command, well within Claude Code's hook timeout defaults.
Own this plugin?
Verify ownership to unlock analytics, metadata editing, and a verified badge.
Sign in to claimOwn this plugin?
Verify ownership to unlock analytics, metadata editing, and a verified badge.
Sign in to claim