Lesson

Guide Claude Code with Rich PreToolUse Feedback

Return structured JSON from PreToolUse hooks to deny commands with clear reasons and suggestions, creating a self-correcting feedback loop.

Access
Included
Transcript
Needs source

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