Guide Claude Code with Rich PreToolUse Feedback

John Lindquist
InstructorJohn Lindquist

Social Share Links

Tweet

Guiding Claude Code with rich feedback turns blocked commands into learning opportunities. Return structured JSON with denial reasons and alternatives so Claude self-corrects in real time.

Exit code vs. JSON output

Exit code 2 (lesson 15):

  • Blocks tool execution
  • Shows error to Claude
  • No structured guidance

JSON output (this lesson):

  • Blocks with permissionDecision: "deny"
  • Provides permissionDecisionReason with clear alternatives
  • Creates a self-correcting feedback loop

Return structured feedback

Use HookJSONOutput to deny with guidance:

import { type PreToolUseHookInput, type HookJSONOutput } from "@anthropic-ai/claude-agent-sdk"

const input = await Bun.stdin.json() as PreToolUseHookInput

type BashToolInput = {
  command: string
  description: string
}

if (input.tool_name === "Bash") {
  const toolInput = input.tool_input as BashToolInput

  if (toolInput.command.startsWith("echo")) {
    const output: HookJSONOutput = {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",  // "allow" | "deny" | "ask"
        permissionDecisionReason: "echo is not allowed. Always use node -e and console.log instead."
      }
    }
    console.log(JSON.stringify(output, null, 2))
  }
}

Key fields:

  • permissionDecision: "allow" (bypass permission), "deny" (block + feedback), or "ask" (prompt user)
  • permissionDecisionReason: Message shown to Claude (for deny) or user (for allow/ask)

See it self-correct

Prompt Claude to use a blocked command:

use echo to echo hello world

Sequence:

  1. Claude attempts echo
  2. Hook denies with reason: "use node -e and console.log"
  3. Claude immediately retries with node -e 'console.log("hello world")'
  4. Command succeeds

Permission decisions

Three options for permissionDecision:

  1. "deny" - Block tool + show reason to Claude Use case: Enforce conventions with guidance

  2. "allow" - Bypass permission system + show reason to user Use case: Auto-approve safe operations

  3. "ask" - Prompt user for confirmation + show reason Use case: Require manual approval for sensitive operations

Why this pattern works

  • Self-correction: Claude learns your conventions in real-time
  • Clear guidance: Explains why something's blocked and what to use instead
  • No dead ends: Every denial includes the path forward
  • Consistent enforcement: Same rules across all sessions

Try it

Prompts:

use echo to echo hello world

[00:00] For more fine-grained control, I'm going to import the hook.json output type. And since these are all types, let's do it this way. And then instead of console error and process exit, we can build up some output and then we'll console.log the output as JSON to send a JSON object back through the API. And we're going to bring in the hook specific output and configure this so that the hook event name is pretool-use, our permission decision is deny, and the reason can be echo is not allowed, always use node and console log instead. I'm going to toss in the dash e for evaluate flag.

[00:46] So if I run clod and ask it to use echo to echo hello world, now we'll get an error that would have shown up if we used console error and exiting the process with the exit code, and we can provide instructions like fallbacks on how to accomplish that task instead. So if you look at Ctrl-O here, you'll see it attempted bash with echo and then it successfully used node to execute console log.