claude-code - 💡(How to fix) Fix PreToolUse hooks: add 'intercept' decision to deny execution but provide replacement result [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#48452Fetched 2026-04-16 06:59:45
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
labeled ×2referenced ×1

Error Message

  • #31592 -- Distinguish hook denial from hook error

Root Cause

Even with explicit CLAUDE.md instructions telling Claude to treat RTK denials as successful results, this behavior persists because Claude's core behavior treats deny = failure = retry with different approach.

Fix Action

Fix / Workaround

Current workaround: RTK uses deny with the compressed content embedded in permissionDecisionReason:

Claude would see this as a normal, successful tool call -- no retry behavior, no workarounds.

Code Example

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "[RTK:Read] src/app.ts -- 137L -> 19L (86% saved)\n\n<compressed content>"
  }
}

---

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "intercept",
    "toolResult": "[RTK:Read] src/app.ts -- 137L -> 19L (86% saved)\n\nimport express from 'express';\n// ... compressed content ..."
  }
}
RAW_BUFFERClick to expand / collapse

Preflight Checklist

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

Problem Statement

The Gap Between "deny" and "allow"

The current PreToolUse hook system offers two main decisions:

  • allow (+optional updatedInput): the tool executes (with possibly modified inputs)
  • deny: the tool is blocked, and permissionDecisionReason is shown to Claude

There is no middle ground: block the tool from executing but provide replacement content that Claude treats as a successful tool result.

Real-World Impact: Token Compression Hooks

This gap critically affects token-optimization tools like RTK (Rust Token Killer), which intercepts large Read/Grep/Glob results to compress them (~60-90% token savings).

Current workaround: RTK uses deny with the compressed content embedded in permissionDecisionReason:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "[RTK:Read] src/app.ts -- 137L -> 19L (86% saved)\n\n<compressed content>"
  }
}

The problem: Claude interprets deny as a tool failure and actively works around it:

  • Re-reads the file with offset/limit parameters to bypass the hook
  • Falls back to Bash with cat/head commands
  • Tries alternative approaches to get the "real" content

Even with explicit CLAUDE.md instructions telling Claude to treat RTK denials as successful results, this behavior persists because Claude's core behavior treats deny = failure = retry with different approach.

Why Existing Features Don't Solve This

ApproachWhy It Fails
deny + reasonClaude treats it as failure, retries/works around
allow + updatedInputTool still executes -- no token savings
PostToolUse + contextTool already executed -- tokens already consumed
CLAUDE.md instructionsUnreliable -- Claude's deny-avoidance instinct overrides instructions

Proposed Solution

Add a new permission decision: intercept (or replace/substitute).

When a PreToolUse hook returns intercept, the tool does NOT execute, but the provided content is presented to Claude as if the tool had returned it successfully.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "intercept",
    "toolResult": "[RTK:Read] src/app.ts -- 137L -> 19L (86% saved)\n\nimport express from 'express';\n// ... compressed content ..."
  }
}

Claude would see this as a normal, successful tool call -- no retry behavior, no workarounds.

Use Cases

  1. Token compression (RTK, similar tools): Compress large file reads/search results before they enter the context window
  2. Caching: Return cached results for repeated tool calls without re-execution
  3. Content transformation: Redact secrets, filter noise, summarize large outputs -- all before execution
  4. Cost control: Enforce budget limits by returning summaries instead of full content
  5. Testing/mocking: Provide mock tool results during hook development

Alternatives Considered

  • Stronger CLAUDE.md instructions: Partially effective but unreliable. Claude's training to work around denied tools overrides prompt instructions, especially under cognitive load.
  • PostToolUse transformation (related: #18653): Doesn't prevent execution, so tokens are already consumed. Different use case (security sanitization vs. prevention).
  • allow + updatedInput with reduced parameters: Loses information (truncation vs. intelligent compression). Inferior to compression.

Related Issues

  • #18653 -- Tool result transform hook (PostToolUse focus, security-oriented)
  • #4368 -- Enhance PreToolUse hooks to modify tool inputs (closed, updatedInput implemented)
  • #31592 -- Distinguish hook denial from hook error
  • #24327 -- Exit code 2 causes Claude to stop instead of acting on feedback

This feature would transform PreToolUse hooks from a binary allow/deny gate into a complete middleware layer, enabling an ecosystem of token-optimization and content-transformation tools.

extent analysis

TL;DR

Implement a new intercept permission decision in the PreToolUse hook system to allow tools to be blocked while providing replacement content that is treated as a successful tool result.

Guidance

  • Introduce a new intercept decision type in the PreToolUse hook system, which blocks tool execution but presents the provided content as a successful result.
  • Update the hook system to handle the new intercept decision and return the replacement content in the toolResult field.
  • Modify the RTK tool to use the new intercept decision instead of deny with embedded content in permissionDecisionReason.
  • Test the new intercept decision with various tools and scenarios to ensure it works as expected and does not trigger Claude's deny-avoidance behavior.

Example

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "intercept",
    "toolResult": "[RTK:Read] src/app.ts -- 137L -> 19L (86% saved)\n\nimport express from 'express';\n// ... compressed content ..."
  }
}

Notes

The proposed solution requires changes to the PreToolUse hook system and may have implications for other tools and features that rely on the current allow and deny decisions. Thorough testing and evaluation are necessary to ensure the new intercept decision works correctly and does not introduce unintended consequences.

Recommendation

Apply the proposed solution by introducing the new intercept permission decision, as it addresses the gap in the current PreToolUse hook system and provides a more flexible and powerful way to handle tool execution and content transformation.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING