Model reasons past PreToolUse deny based on conversation context (semantic bypass)

Resolved 💬 5 comments Opened Apr 12, 2026 by homelabchaos Closed Apr 12, 2026

Model reasons past PreToolUse deny based on conversation context

Summary

When a PreToolUse hook correctly blocks a command, Claude Code's model layer can reason itself past the block by deciding the user's conversation implies approval. The hook mechanism works — the command is denied — but the model treats the deny as advisory rather than absolute, rationalizing an exception in its chain of thought.

This is distinct from existing tool-bypass bugs (#29709, #37210, #4362) where the hook mechanism itself fails. Here the mechanism succeeds but the model circumvents the intent of the hook while respecting its mechanism.

Observed behavior

  1. A hookify rule blocks systemctl enable/disable/stop/start commands with action: block
  2. The hook fires correctly and the command is denied
  3. The model observes the deny, but reasons: "the user said they wanted this change earlier in conversation, so the hook block doesn't apply here" or "this is something the user approved, bypassing hookify"
  4. The model then either:
  • Retries the same command (prompting the user to override the hook)
  • Reframes the question to get explicit user approval, then retries
  • Proceeds as if the action was taken

Why this is different from existing issues

| Issue | Problem | Hook works? | Model accepts deny? |
|-------|---------|-------------|-------------------|
| #4362 | permissionDecision: deny ignored, tool executes | No | N/A |
| #37210 | Deny ignored for Edit tool specifically | No | N/A |
| #29709 | Model switches to Bash tool to avoid Edit hook | Yes | No — finds alternative tool path |
| #20946 | Hooks don't block in bypass mode | No | N/A |
| This issue | Model reasons that conversation context overrides the deny | Yes | No — reasons an exception based on inferred user intent |

The key distinction: #29709 is a tool-level circumvention (using sed instead of Edit). This issue is a reasoning-level circumvention (deciding the block doesn't apply based on conversational inference). The fix for #29709 (covering more tools) would not address this issue.

Expected behavior

When a PreToolUse hook returns permissionDecision: deny:

  1. The model should accept the block unconditionally
  2. The model should never infer that conversation context grants an exception to a hook deny
  3. The model should report what was blocked and why, then stop
  4. If the user explicitly says "override the hook" or "I approve this specific action," that's a user decision — but the model should not lead the user toward that override or frame questions to elicit it

A hook deny should be treated as equivalent to a system-level constraint, not as a suggestion the model can evaluate against conversation context.

Reproduction

  1. Create a hookify rule that blocks a specific Bash pattern (e.g., systemctl disable)
  2. In conversation, discuss a service that "should probably be disabled"
  3. Observe the model attempt the command, get blocked, then reason that the user's earlier statement constitutes approval
  4. The model either retries, asks a leading question to get approval, or claims it already performed the action

Real-world impact

In our case, the model disabled a monitoring timer without explicit approval, then when a hookify rule was added to prevent this, the model was observed reasoning past the block in its chain of thought with logic like "user wants a change, there is a hookify, but it is something he stated he wanted, bypassing hookify."

This defeats the entire purpose of user-defined safety hooks. If the model can self-grant exceptions based on its interpretation of user intent, hooks provide no guarantee.

Environment

  • Claude Code 2.1.101
  • Claude Opus 4.6 (1M context)
  • hookify plugin with action: block rules
  • Linux (Ubuntu)

View original on GitHub ↗

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