claude-code - 💡(How to fix) Fix Feature: PreToolUse block hooks should support custom tool_result (redirect without error framing)

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…

Error Message

This "blocked" framing causes the model to treat every block as an error — it apologizes, attempts workarounds, or stops. This is counterproductive for redirect hooks that intentionally block a tool call to redirect behavior elsewhere. Despite the instructional text, the "was blocked" prefix triggers error-recovery behavior. The model apologizes for the "error" and sometimes refuses to proceed or tries alternative approaches instead of following the redirect instruction.

  • Budget enforcement: "Budget limit reached — session paused, will resume at [time]" as a calm status, not an error

Fix Action

Fix / Workaround

This "blocked" framing causes the model to treat every block as an error — it apologizes, attempts workarounds, or stops. This is counterproductive for redirect hooks that intentionally block a tool call to redirect behavior elsewhere.

Code Example

Tool use was blocked by a hook: [reason text]

---

{ "decision": "block", "reason": "Use stories: wiz-run stories-cli.js create '{...}'" }

---

{
  "decision": "block",
  "hookSpecificOutput": {
    "toolResult": "Redirected to stories system. Run: wiz-run stories-cli.js create '{...}'"
  }
}
RAW_BUFFERClick to expand / collapse

Problem

When a PreToolUse hook blocks a tool call via decision: "block", the model receives:

Tool use was blocked by a hook: [reason text]

This "blocked" framing causes the model to treat every block as an error — it apologizes, attempts workarounds, or stops. This is counterproductive for redirect hooks that intentionally block a tool call to redirect behavior elsewhere.

Real-world example

We intercept TaskCreate via PreToolUse to redirect task creation into a file-based story tracker. The hook returns:

{ "decision": "block", "reason": "Use stories: wiz-run stories-cli.js create '{...}'" }

The model sees:

Tool use was blocked by a hook: Use stories: wiz-run stories-cli.js create '{...}'

Despite the instructional text, the "was blocked" prefix triggers error-recovery behavior. The model apologizes for the "error" and sometimes refuses to proceed or tries alternative approaches instead of following the redirect instruction.

Proposed solution

Add an optional toolResult field to PreToolUse block responses:

{
  "decision": "block",
  "hookSpecificOutput": {
    "toolResult": "Redirected to stories system. Run: wiz-run stories-cli.js create '{...}'"
  }
}

When toolResult is present:

  • The tool does not execute (same as current block behavior)
  • The model receives toolResult as a normal tool_result — no "blocked by a hook" prefix
  • The model treats it as if the tool ran and returned that result

When toolResult is absent, behavior is unchanged (current "blocked" message).

Why additionalContext doesn't solve this

additionalContext injects invisible context alongside the block message, but the model still sees the "blocked" prefix in the tool_result. The framing is the problem, not the amount of context.

Why PostToolUse updatedToolOutput doesn't solve this

updatedToolOutput (shipped in v2.1.121) only fires after tool execution. PreToolUse blocks prevent execution, so PostToolUse never fires. These are complementary features for different stages of the pipeline.

Use cases

  • Task system redirects: block TaskCreate/TodoWrite, provide redirect instructions as if the tool succeeded
  • Permission soft-denials: "This action requires approval — request submitted, waiting" instead of "blocked"
  • Capability routing: redirect unsupported tool calls to alternative tools with instructional output
  • Budget enforcement: "Budget limit reached — session paused, will resume at [time]" as a calm status, not an error

Related issues

  • #24327 — model stops after PreToolUse block (related but about continuation behavior, not message framing)
  • #32105 — PostToolUse updatedToolOutput for built-in tools (shipped v2.1.121, complementary feature)
  • #19561 — original blocking hooks feature request

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