[FEATURE] Permission deny list: blacklist specific commands without LLM hooks

Resolved 💬 6 comments Opened Feb 19, 2026 by marcospgp Closed May 1, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

When running Claude Code with --dangerously-skip-permissions (because manual approval on every tool call is impractical for real workflows), there's no way to blacklist specific dangerous commands while allowing everything else.

The current hook system (PreToolUse) is the only mechanism to block commands, but it requires an LLM-powered judgment call on every tool invocation. This causes:

  1. High false-positive rate: Hooks that try to block patterns like git push --force or drizzle-kit push frequently misfire on completely safe commands (git push origin main blocked because the hook LLM sees "push" and gets confused). The hook output literally shows the LLM arguing with itself about whether to allow the command.
  1. All-or-nothing permissions: The only built-in options are "allow everything" (--dangerously-skip-permissions) or "prompt for everything" (default). There's no middle ground for "allow everything EXCEPT these specific patterns."
  1. Overhead: Every Bash tool call pays the latency cost of the hook evaluation, even for ls or git status.

The most common real-world need is simple: "run autonomously, but never execute these 5 specific command patterns." This should be trivial to configure, but currently requires a fragile hook setup that generates more noise than protection.

Proposed Solution

Add a deny_patterns (or blocked_commands) option to settings.json or .claude/settings.json that takes a list of regex or glob patterns to match against Bash commands. Any matching command is blocked with a clear error message. No LLM evaluation needed.

{
  "permissions": {
    "deny": [
      "git push --force",
      "git reset --hard",
      "drizzle-kit push",
      "drizzle-kit migrate",
      "rm -rf",
      "sudo *"
    ]
  }
}

Behavior:

  • Patterns matched as substrings or globs against the full command string
  • Blocked commands show a clear message: "Command blocked by deny rule: <pattern>"
  • No LLM evaluation, no latency overhead, no false positives
  • Works alongside --dangerously-skip-permissions (deny rules still enforced)
  • Scoped per-project (.claude/settings.json) or global (~/.claude/settings.json)

Alternative Solutions

  • Current PreToolUse hooks: Work in theory but use LLM-based evaluation that produces frequent false positives. A hook blocking push --force will sometimes block git push origin main because the LLM sees "push" and deliberates. The hook output shows the LLM literally reasoning "this is not force push... but it involves pushing... when in doubt, ALLOW... but wait..." before ultimately blocking anyway.
  • Default permission mode (no --dangerously-skip-permissions): Requires manual approval for every single tool call. Completely impractical for any real workflow.
  • Allowlist approach: Some users create allowlists of permitted commands, but this is impossibly verbose. You'd need to enumerate every possible git, npm, bun, ls, grep invocation. The common case is "allow everything except 5 things," not "block everything except 500 things."

Priority

Critical - Blocking my work

Feature Category

Configuration and settings

Use Case Example

  1. I work on a project with a production database, Stripe integration, and git repo
  2. I want Claude to run autonomously (--dangerously-skip-permissions) for productivity
  3. But I need to prevent: drizzle-kit push/migrate (untracked DB mutations), git push --force (destructive), sudo (privilege escalation)
  4. I configure 5 deny patterns in .claude/settings.json
  5. Claude runs freely for 99.9% of operations, hard-blocked on the 5 dangerous ones
  6. No false positives, no latency overhead, no LLM deliberation on every command

Additional Context

The PreToolUse hook's LLM-based approach is fundamentally wrong for command blocking. Pattern matching is a solved problem. When I write a rule "block push --force," I mean the literal string push --force in the command. I don't need an LLM to interpret whether git push origin main is "kind of like" a force push. The current system adds complexity, latency, and unreliability to something that should be a simple string match.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗