PermissionDenied hook event for automated agent self-correction
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
When a human supervisor denies a permission prompt, the agent receives no programmatic signal — only a generic "tool use was denied" message in the conversation. This causes a recurring pattern in plugin-heavy setups:
- Agent drifts from instructed tool usage (e.g., calls
python3 script.pyinstead of calling the script directly via its shebang, or usesBash(mkdir -p && script)instead of separate tool calls) - The drifted invocation doesn't match any allow rule — permission prompt fires
- Human denies — agent retries a variation that also doesn't match — another denial
- Human must manually type guidance ("use the skill instead", "split the commands")
This is especially painful with hooks and skills that define specific CLI patterns. The agent was instructed on the correct patterns, but drifted. A hook firing on denial could inject a systemMessage reminding the agent of the correct approach — closing the feedback loop automatically.
Current workaround: Correlate PreToolUse logs against PostToolUse logs by tool_use_id to infer denials (fragile, can't inject guidance back into the conversation).
Proposed Solution
Add a PermissionDenied (or ToolUseDenied) hook event that fires when the user denies a permission prompt. The hook should support returning a systemMessage that gets injected into the agent's conversation context — the same response contract as PreToolUse hooks.
Hook input:
{
"hook_event_name": "PermissionDenied",
"session_id": "abc123",
"tool_name": "Bash",
"tool_input": { "command": "python3 ~/.claude/tools/mktmp.py git msg .txt" },
"tool_use_id": "toolu_01ABC...",
"user_comment": "use the script directly, not python3"
}
Hook response (stdout = systemMessage):
#!/bin/bash
# Hook: on-permission-denied.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Detect common drift patterns and inject correction
if [[ "$TOOL" == "Bash" && "$COMMAND" == python3* ]]; then
cat <<'MSG'
REMINDER: Scripts in ~/.claude/tools/ are self-executable via shebang.
Call them directly: ~/.claude/tools/script.py args
Never prefix with python3 or uv run --script.
MSG
exit 0
fi
Key requirement: The hook's stdout must be delivered to the agent as a systemMessage, just like PreToolUse hooks can. Without this, the event is useful for logging but doesn't close the self-correction loop.
Use Cases
- Drift correction — Agent uses
python3 script.pyinstead of direct execution; hook reminds it of the shebang pattern - Command chaining correction — Agent chains with
&&; hook reminds it to use separate Bash calls - Skill redirection — Agent calls
gh pr createdirectly instead of theDev10x:gh-pr-createskill; hook points it to the skill - Pattern library — Plugin authors can ship a library of common drift patterns with corrective
systemMessageresponses, reducing human interruption across all users
Alternative Solutions
- Manual correction each time — User types guidance after each denial. Works but doesn't scale with 50+ skills.
- #37153 proposes a
ToolUseDeniedevent — covers the event trigger but focuses on observability/logging, notsystemMessageinjection back to the agent. - #29782 proposes user-typed reasons at denial time — complementary but manual per-denial, not automated pattern matching.
- #21959 proposes
PermissionRequest/PermissionResponsefor metrics — broader lifecycle hooks, different goal.
This request specifically needs the systemMessage injection capability on the denial event, which none of the above explicitly request.
Priority
High - Significant impact on productivity
Feature Category
Hooks and automation
Use Case Example
- Agent attempts
Bash(mkdir -p /tmp/foo && ~/.claude/tools/mktmp.py git msg .txt) &&chaining doesn't match allow rule — permission prompt fires- User denies
PermissionDeniedhook fires, pattern-matches the&&chaining- Hook returns: "Never chain commands with &&. Each command must be a separate Bash tool call."
- Agent immediately self-corrects: makes two separate Bash calls
- Both match allow rules — no further prompts needed
Additional Context
In a plugin-heavy setup (50+ skills, 15+ hooks), permission friction from agent drift is the #1 source of human interruption. A self-correcting denial hook would reduce these interruptions by an estimated 60-80%, as most denials follow predictable drift patterns that have known corrections.
Related issues:
- #37153 —
ToolUseDeniedevent (observability-focused, nosystemMessage) - #29782 — User-typed denial reasons (manual, per-denial)
- #21959 —
PermissionRequest/PermissionResponsefor metrics
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗