PreToolUse hooks: add 'intercept' decision to deny execution but provide replacement result
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
The Gap Between "deny" and "allow"
The current PreToolUse hook system offers two main decisions:
allow(+optionalupdatedInput): the tool executes (with possibly modified inputs)deny: the tool is blocked, andpermissionDecisionReasonis 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/limitparameters to bypass the hook - Falls back to
Bashwithcat/headcommands - 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
| Approach | Why It Fails |
|----------|-------------|
| deny + reason | Claude treats it as failure, retries/works around |
| allow + updatedInput | Tool still executes -- no token savings |
| PostToolUse + context | Tool already executed -- tokens already consumed |
| CLAUDE.md instructions | Unreliable -- 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
- Token compression (RTK, similar tools): Compress large file reads/search results before they enter the context window
- Caching: Return cached results for repeated tool calls without re-execution
- Content transformation: Redact secrets, filter noise, summarize large outputs -- all before execution
- Cost control: Enforce budget limits by returning summaries instead of full content
- 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+updatedInputwith 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,
updatedInputimplemented) - #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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗