Feature: PreToolUse block hooks should support custom tool_result (redirect without error framing)

Resolved 💬 3 comments Opened May 7, 2026 by sstraus Closed May 10, 2026

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

View original on GitHub ↗

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