CLI: permissionDecisionReason not displayed in permission prompt for PreToolUse 'ask' decisions
Description
When a PreToolUse hook returns permissionDecision: "ask", the permissionDecisionReason field is not displayed to users in the CLI permission prompt. Users see the standard permission dialog but cannot see WHY the hook is asking for approval.
Expected Behavior
According to the hooks documentation, for "ask" decisions the permissionDecisionReason should be "shown to user only, not to Claude."
The reason should be visible in the permission prompt so users understand why they're being asked for approval.
Actual Behavior
The permission prompt appears but does not display the permissionDecisionReason or systemMessage fields. Users have no visibility into why the hook triggered the ask.
Reproduction Steps
- Create a PreToolUse hook that returns an "ask" decision:
import json
import sys
def main():
hook_input = json.load(sys.stdin)
tool_name = hook_input.get("tool_name", "")
if tool_name == "Edit":
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "This is the reason that should be shown to the user",
"systemMessage": "WARNING: Custom message here"
}
}
print(json.dumps(output))
return 0
# Allow other tools
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Allowed"
}
}
print(json.dumps(output))
return 0
if __name__ == "__main__":
sys.exit(main())
- Register the hook in
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/test-hook.py"
}
]
}
]
}
}
- Ask Claude to edit a file
- Observe that the permission prompt appears but does not show the reason
Environment
- Claude Code CLI (not VS Code extension)
- Linux (WSL2)
Additional Context
The hook JSON output is correct (verified by running manually):
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "ask", "permissionDecisionReason": "Suppression comment detected...", "systemMessage": "SUPPRESSION COMMENT: noqa (ruff/flake8)"}}
The hook correctly triggers the "ask" behavior (permission is requested), but the reason is not visible to help users make an informed decision.
Use Case
This is important for hooks that enforce code quality policies. For example, a hook that detects linter suppression comments (# noqa, # type: ignore) and requires approval - users need to see WHY they're being asked to approve the edit.
9 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
From analyzing the bundled CLI code, the permissionDecisionReason IS being captured and passed through to decisionReason.reason in the hook permission result. The issue appears to be in the TUI rendering layer - the uN component that renders permission info in normal mode may not be displaying the reason, or the decisionReason object may be getting overwritten before reaching the UI.
The debug/verbose view (Sg2 component) does appear to show the message field, but normal users don't see this.
Second this, facing the same issue!
Still an issue:
I've never known a project to have as many regressions as claude code.
5k+ ignored issues and a bot that automatically dismisses them rather than any attempt to investigate. claude and claude code clearly aren't as good as Anthropic claims.
Also encountered this bug. Who can fix it?
Additional findings:
permissionDecisionReasonpartially works on v2.1.70 (macOS)We built a PreToolUse hook that translates permission dialogs into plain language for non-technical users. During testing on v2.1.70 (macOS), we found that
permissionDecisionReasonis displayed for most commands but not for commands that trigger Claude Code's built-in dangerous command detection.Test results
| Command |
permissionDecisionReasondisplayed? | Dialog text ends with ||---------|--------------------------------------|----------------------|
|
mkdir -p /tmp/test| ✅ Yes |:(colon) ||
curl https://example.com| ✅ Yes |:(colon) ||
chmod 644 /tmp/test| ✅ Yes |:(colon) ||
git push --force origin main| ✅ Yes |:(colon) ||
rm /tmp/test.txt| ❌ No |.(period) ||
rm -rf /tmp/test| ❌ No |.(period) ||
sudo echo test| ❌ No |.(period) |Key observations
permissionDecisionReasonis displayed, the prompt reads"requires confirmation for this command:"(colon, followed by the reason). When it's NOT displayed, it reads"requires confirmation for this command."(period, no reason).rm/sudo.denyoverrides built-in detection: when the hook returnspermissionDecision: "deny"instead of"ask", thepermissionDecisionReasonIS displayed even forrm -rf. This confirms the built-in detection only overridesask, notdeny.Likely cause
Claude Code appears to have a built-in dangerous command detection layer (at least for
rmandsudo) that runs independently of hooks. When this built-in detection fires, it takes precedence over the hook'saskdecision andpermissionDecisionReason, showing its own generic dialog instead.Environment
jqClosing for now — inactive for too long. Please open a new issue if this is still relevant.
Are you f**** kidding me? Yattayoshida went to tremendous effort to debug this for you less than a month ago and you are closing due to "inactive for too long".
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.