Bug: Certain characters in permissions.deny rules silently break all hooks

Resolved 💬 2 comments Opened Dec 10, 2025 by mlaaaaden Closed Dec 11, 2025

Bug: Certain characters in permissions.deny rules silently break all hooks

Summary

Specific characters in permissions.deny rules cause Claude Code to silently ignore the entire hooks configuration. The /hooks UI shows "No matchers configured yet" even though hooks are properly defined in settings.json.

Environment

  • Claude Code version: v2.0.64
  • OS: Ubuntu Linux (also reproduced concept on other systems)
  • Shell: bash

Steps to Reproduce

  1. Create a ~/.claude/settings.json with a problematic deny rule:
{
  "permissions": {
    "allow": ["Bash"],
    "deny": [
      "Bash(curl *| bash)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {"type": "command", "command": "echo 'hook fired'"}
        ]
      }
    ]
  }
}
  1. Start Claude Code: claude
  2. Type /hooks and select PreToolUse

Expected: Shows [User] * with 1 hook
Actual: Shows No matchers configured yet

Root Cause Analysis

Through extensive binary search testing (30+ isolated tests), we identified that the following patterns in deny rules break hook parsing:

| Pattern | Example | Breaking Character(s) |
|---------|---------|----------------------|
| Pipe to command | Bash(curl *\| bash) | \| |
| Fork bomb | Bash(:(){ :\|:& };:) | { } ; : & \| |
| Trailing wildcard | Bash(shred -vfz *) | * at end (but /* is OK) |
| Embedded quotes | Bash(perl -e 'fork') | '...' |

Working Patterns (for reference)

These patterns work correctly:

  • Bash(rm -rf /) - simple commands
  • Bash(rm -rf /*) - path wildcards (trailing /*)
  • Bash(git push --force origin main) - flags and arguments
  • Bash(rm -rf $HOME) - environment variables

Impact

  • Severity: High - All hooks silently stop working
  • No error messages are displayed
  • /hooks UI misleadingly shows "No matchers configured yet"
  • Users have no indication their hooks configuration is being ignored

Workaround

Remove deny rules containing problematic characters. We created a validation script to detect these patterns:

const BREAKING_PATTERNS = [
  { pattern: /\|\s*\w+\)$/, desc: "pipe to command" },
  { pattern: /[{}]/, desc: "curly braces" },
  { pattern: /;\s*:/, desc: "semicolon-colon combo" },
  { pattern: /[^/]\*\)$/, desc: "trailing wildcard (not /*)" },
  { pattern: /'\s*[^']+\s*'\)$/, desc: "embedded quotes" },
];

Suggested Fix

The deny rule parser should properly escape special regex/shell characters, or provide clear error messages when it encounters patterns it cannot parse.

Additional Context

  • The hooks themselves work correctly when tested manually via stdin
  • The JSON is valid (verified with multiple JSON validators)
  • No BOM or encoding issues (verified with xxd)
  • The failure is completely silent - no logs, no errors

Test Methodology

We performed systematic binary search testing:

  1. Started with minimal working config (hooks only) ✓
  2. Added top-level keys one by one ✓
  3. Added full permissions with simple deny rules ✓
  4. Added full deny rules → BROKEN
  5. Binary searched deny rules to isolate culprits
  6. Confirmed each culprit with single-rule tests

Total tests performed: 30+

View original on GitHub ↗

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