CLI: permissionDecisionReason not displayed in permission prompt for PreToolUse 'ask' decisions

Status Closed — not planned
Maintainer reply None cached
Activity 9 comments · opened Jan 10, 2026 · closed Apr 3, 2026

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

  1. 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())
  1. Register the hook in .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/test-hook.py"
          }
        ]
      }
    ]
  }
}
  1. Ask Claude to edit a file
  2. 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.

View original on GitHub ↗

9 Comments

github-actions[bot] · 7 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/10062

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

mruwnik · 7 months ago

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.

mathew55 · 6 months ago

Second this, facing the same issue!

StudioLE · 6 months ago

Still an issue:

$ claude --version
2.1.63 (Claude Code)

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.

yejh123 · 5 months ago

Also encountered this bug. Who can fix it?

yottayoshida · 5 months ago

Additional findings: permissionDecisionReason partially 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 permissionDecisionReason is displayed for most commands but not for commands that trigger Claude Code's built-in dangerous command detection.

Test results

| Command | permissionDecisionReason displayed? | 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

  1. The dialog text ending differs: when permissionDecisionReason is 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).
  1. The hook IS executed in all cases (confirmed via log file), and produces correct JSON output — but the output is discarded for rm/sudo.
  1. deny overrides built-in detection: when the hook returns permissionDecision: "deny" instead of "ask", the permissionDecisionReason IS displayed even for rm -rf. This confirms the built-in detection only overrides ask, not deny.

Likely cause

Claude Code appears to have a built-in dangerous command detection layer (at least for rm and sudo) that runs independently of hooks. When this built-in detection fires, it takes precedence over the hook's ask decision and permissionDecisionReason, showing its own generic dialog instead.

Environment

  • Claude Code v2.1.70
  • macOS (Darwin 23.6.0)
  • Hook: bash script returning JSON via jq
github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

mbylstra · 4 months ago
Closing 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".

github-actions[bot] · 4 months ago

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.