Feature Request: Add "substitute" decision type to PreToolUse hooks for result injection

Resolved 💬 4 comments Opened Feb 18, 2026 by AllCytes Closed Mar 21, 2026

Hey team,

So I've been building out a middleware caching layer on top of Claude Code's hook system, and I ran into a protocol limitation that I think would be a pretty big unlock if addressed.

What I built:

I have a full caching infrastructure that sits in the PreToolUse/PostToolUse hooks. It caches results from Read, Grep, Glob, and WebFetch tool calls in SQLite (WAL mode), with:

  • Per-terminal session isolation (so multiple Claude Code instances don't step on each other)
  • TTL-based expiry (configurable per tool, e.g. Read = 300s, WebFetch = 1800s)
  • Automatic cache invalidation when Edit/Write modifies a file (purges stale Read entries)
  • Windows path normalization (D:\ and D:/ produce the same cache key)
  • Fail-open design (cache errors never block tool execution)
  • Performance around ~3.5ms avg per cache operation

The whole thing works. Storage, retrieval, invalidation, TTL, cleanup, all of it. I've got 88 unit + integration tests passing across the caching layer and the intelligence features built on top of it.

The problem:

The PreToolUse hook protocol currently supports three decisions:

{}                          // Allow (tool runs normally)
{"decision": "block"}       // Deny (tool is blocked entirely)
{"decision": "approve"}     // Skip user confirmation (tool still runs)

None of these let me say: "Hey, I already have the result for this tool call, use this instead of running the tool again."

I tried using {"decision": "block", "systemMessage": cached_content} to inject the cached result, but the LLM sees it as "Blocked by hook" rather than treating it as the tool's output. So it thinks the tool failed instead of getting the cached data.

What I'm requesting:

A 4th decision type, something like:

{
  "decision": "substitute",
  "result": "cached tool output here"
}

This would tell Claude Code: "Don't execute the tool, just use this result as if the tool ran and returned it."

Why this matters:

I ran a simulation of a typical refactoring session (read files, re-read for implementation, edit, re-read after edit, etc.) and the cache hit rate was 50%. That's half the tool calls being redundant file reads or grep searches that could be served from cache in 3.5ms instead of hitting the filesystem again.

For longer sessions where the LLM keeps re-reading the same files, this could be a significant reduction in latency and token waste. The infrastructure is 100% built and ready to go. Literally just need this one protocol addition and I can flip it on.

Implementation note:

From my side, the PreToolUse hook would just need to return the substitute decision with the cached response. The hook already computes the cache key, checks for a hit, and has the cached content ready. It's one if block away from being active.

Thanks for considering this. I think the hook system is already really powerful, and this addition would open up a whole category of optimizations that aren't possible today.

Tony

View original on GitHub ↗

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