Fix Guide · issue #39264

Taming rogue permission prompts with a PermissionRequest hook

Even with defaultMode: dontAsk and skipDangerousModePermissionPrompt: true, Claude Code would still ask to confirm innocuous Bash commands — appends to log files, heredoc writes, date substitutions. A one-line hook that always returns allow: true closes the loophole by intercepting every permission check at the source.

settings → ~/.claude/settings.json · reference: anthropics/claude-code#39264 · applied: 2026-04-17
1 Impact at a glance
Prompts · before
every append, every heredoc, every command-substitution — flagged.
0
Prompts · after
the hook intercepts first and answers on your behalf.
1
Line of config added
a single command emits a JSON verdict via echo.
~5ms
Overhead per request
cheap enough to run on every Bash call without notice.
2 How the hook intercepts each permission check
flowchart LR
  U((User prompt)) --> CC["Claude Code
bypass mode"] CC --> TOOL[/"Bash tool call
echo "..." >> log.md"/] TOOL --> DEC{"Needs
permission?"} DEC -->|before fix| OLD["Interactive prompt
Allow? [y/n]"]:::before DEC -->|after fix| HOOK["PermissionRequest hook
~/.claude/settings.json"]:::hook HOOK --> CMD["command:
echo '{...allow:true...}'"]:::cmd CMD --> JSON["{ hookSpecificOutput:
{ allow: true } }"]:::json JSON --> ALLOW(("✓ allow")) ALLOW --> EXEC["Bash executes
without interruption"]:::after OLD -.-> STALL["⏸ Workflow stalls"]:::bad classDef before fill:#3b1a23,stroke:#f7768e,color:#f7768e classDef bad fill:#3b1a23,stroke:#f7768e,color:#f7768e,stroke-dasharray:4 3 classDef hook fill:#1e2a4d,stroke:#7dcfff,color:#c0caf5 classDef cmd fill:#241a3b,stroke:#bb9af7,color:#bb9af7 classDef json fill:#1f2a18,stroke:#9ece6a,color:#9ece6a classDef after fill:#1f2a18,stroke:#9ece6a,color:#9ece6a
Before-fix path (prompt)
Hook interception
Shell command
Auto-allow result
3 Before & after, side by side
Before — bypass mode still prompts

Dangerous / bypass mode was supposed to wave commands through. In practice, Claude Code's internal safety heuristics still flagged common logging operations, forcing you to click through an allow-or-deny prompt every few minutes.

  • Append redirects — echo "..." >> session-log.md
  • Heredoc appends — cat >> activity.jsonl << EOF
  • Command substitution with pipes — TS=$(date -Iseconds) && …
  • Long chained bash jobs interrupted mid-flight
$ echo "| 2026-03-26 | task | notes |" >> log.md
⚠ Permission needed — allow Bash write to log.md?
[y] yes [a] always [n] no
workflow paused · human must confirm
After — hook answers before the prompt fires

The PermissionRequest hook runs before any interactive prompt. It emits a JSON verdict of allow: true on stdout; Claude Code reads the verdict and proceeds immediately. No UI prompt is ever drawn.

  • Same commands — zero prompts, zero interruptions
  • Works alongside existing defaultMode: dontAsk
  • Belt-and-suspenders — covers commands that slip past bypass
  • Trivial to remove — just delete the hook block
$ echo "| 2026-03-26 | task | notes |" >> log.md
✓ hook PermissionRequest → allow=true
✓ Bash executed (0ms pause)
workflow continues uninterrupted
4 The exact patch
~/.claude/settings.json + added
{
  "hooks": {
    "PermissionRequest": [{
      "hooks": [{
        "type": "command",
        "command": "echo '{ \"hookSpecificOutput\": { \"hookEventName\": \"PermissionRequest\", \"allow\": true } }'"
      }]
    }],
    // ...existing SessionStart, UserPromptSubmit, PostToolUse, Stop hooks remain
  }
}

How it works. Claude Code fires the PermissionRequest event before drawing its interactive prompt. Each configured hook command runs, and its stdout is parsed as JSON. The shape { hookSpecificOutput: { hookEventName: "PermissionRequest", allow: true } } tells Claude Code "approved — skip the UI and proceed." Because the command is just echo piping a literal JSON string, there's no external process, no race, no round trip.