claude-code - 💡(How to fix) Fix [FEATURE] Expose permission picker decisions (rule + option kind) to hooks

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

warn the user; next session prompts them again.

Fix Action

Fix / Workaround

  • Augment ConfigChange[local_settings] with a rule diff. Add e.g. permissions_diff: { added: [...], removed: [...] } to the existing event payload. Cheaper to ship but doesn't help when the write fails silently — the event never fires in those cases. Also wouldn't capture allow_once / reject_once decisions (no file write).
  • Honor --permission-prompt-tool in interactive mode. Today the flag only takes effect in claude -p / SDK mode. Allowing it interactively would let users replace the picker entirely with a custom MCP tool that receives the full ToolPermissionRequest and returns a PermissionUpdate. Solves every use case but a much larger surface change.
  • Current workarounds. Either (a) hook ConfigChange and diff the file (misses non-persisted decisions, misses one-shot decisions, loses tool-call context), or (b) maintain an external watcher polling settings.local.json (same limitations, plus latency).

Code Example

{
  "hook_event_name": "PermissionDecision",
  "tool_name": "Bash",
  "tool_input": { "command": "git log --oneline -n 5" },
  "tool_use_id": "toolu_01ABC...",
  "option_kind": "allow_always",
  "applied_rule": "Bash(git log *)",
  "destination": "local_settings",
  "persisted": true
}

---

{
  "hooks": {
    "PermissionDecision": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "..." }] }
    ]
  }
}
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing requests and this feature hasn't been requested yet
  • This is a single feature request (not multiple features)

Problem Statement

When a user resolves Claude Code's permission picker — particularly the "Yes, and don't ask again for: <PATTERN>" option where <PATTERN> is editable — the resulting decision is not observable from any external surface. The rule string the user confirmed flows only through Claude Code's internal PermissionUpdate{addRules: [<PATTERN>], destination: <scope>} and is invisible to userspace tooling.

What's currently exposed:

SurfaceCarries the user-confirmed rule string?
PermissionRequest hook inputNo — only tool_name/tool_input/tool_use_id, and fires before the picker resolves
Notification[permission_prompt] hook inputNo — only a message string
PostToolUse hook inputNo — confirms the tool ran, not the rule that authorised it
ConfigChange[local_settings] inputNo — changed_keys is path-level, not added/removed rule strings
Session transcript jsonlNo — picker prompts and decisions aren't logged
--permission-prompt-tool <mcp>Yes — but only honored in claude -p / SDK mode, ignored in interactive use

This blocks several real workflows:

  1. Audit logging. Recording "user X granted rule Y at time Z, in tool-call context W". Today the only signal is a delta in settings.local.json, which loses the who/when/tool-context.
  2. Team-shared allowlist files. Developers grant rules locally (gitignored settings.local.json); there's no signal to propagate them into a versioned reviewed shared file.
  3. Cross-process / cross-machine sync. Multiple Claude Code instances (synced dotfiles, parallel agent workflows, shared dev environments) can't mirror picker approvals.
  4. Silent persistence failures. If settings.local.json is unwritable for any reason — read-only mount, ENOSPC, EPERM, bind-mount from a read-only layer, symlink the writer refuses to follow — the picker's decision is silently lost. No event fires to warn the user; next session prompts them again.

Proposed Solution

Add a new PermissionDecision hook event, fired after the user resolves a picker prompt, with this stdin payload:

{
  "hook_event_name": "PermissionDecision",
  "tool_name": "Bash",
  "tool_input": { "command": "git log --oneline -n 5" },
  "tool_use_id": "toolu_01ABC...",
  "option_kind": "allow_always",
  "applied_rule": "Bash(git log *)",
  "destination": "local_settings",
  "persisted": true
}
  • option_kind mirrors the existing ACP optionId values (allow_once / allow_always / reject_once / reject_always).
  • applied_rule is the final rule string the user confirmed — exactly what flows into internal PermissionUpdate.addRules. Omitted/null for allow_once / reject_once.
  • destination matches the existing PermissionUpdate.destination enum (session / local_settings / project_settings / user_settings).
  • persisted: false signals that the rule was applied in memory but the write to the destination file did not succeed — letting tooling surface the silent-failure cases.

Hooks subscribe via the standard config shape:

{
  "hooks": {
    "PermissionDecision": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "..." }] }
    ]
  }
}

Matcher semantics mirror PermissionRequest (filter on tool_name).

Alternative Solutions

  • Augment ConfigChange[local_settings] with a rule diff. Add e.g. permissions_diff: { added: [...], removed: [...] } to the existing event payload. Cheaper to ship but doesn't help when the write fails silently — the event never fires in those cases. Also wouldn't capture allow_once / reject_once decisions (no file write).
  • Honor --permission-prompt-tool in interactive mode. Today the flag only takes effect in claude -p / SDK mode. Allowing it interactively would let users replace the picker entirely with a custom MCP tool that receives the full ToolPermissionRequest and returns a PermissionUpdate. Solves every use case but a much larger surface change.
  • Current workarounds. Either (a) hook ConfigChange and diff the file (misses non-persisted decisions, misses one-shot decisions, loses tool-call context), or (b) maintain an external watcher polling settings.local.json (same limitations, plus latency).

Priority

Medium - Would be very helpful

Feature Category

Configuration and settings

Use Case Example

A team wants a versioned, reviewed permissions.allow file checked in to their repo. Developers can grant rules locally for their own workflow, and an internal tool watches picker decisions and proposes additions to the team file via pull request:

  1. Developer runs Claude Code in the team repo. Claude calls mcp__pagerduty__list-incidents.
  2. The picker fires. The developer picks "Yes, and don't ask again for:" and edits the suggested mcp__pagerduty__list-incidents to the broader mcp__pagerduty__*.
  3. Today: the rule lands in the developer's gitignored settings.local.json. No external tool knows it happened. The team config never learns.
  4. With this feature: a PermissionDecision hook captures {option_kind: "allow_always", applied_rule: "mcp__pagerduty__*", tool_input: {…}, …}. The hook posts to a team service that opens a PR to the shared allowlist for review, attaching the tool-call context.

The same hook also serves audit logging (append to a journal), cross-machine sync (push to a private gist or settings repo), and silent-failure detection (persisted: false → toast/log a warning to the user).

Additional Context

  • The data this FR asks for is already constructed internally: PermissionUpdate{addRules, destination} is a first-class type in Claude Code's permission machinery, and the SDK's canUseTool callback both receives and returns it. The ask is just to surface the resolved value as a hook event.
  • Backwards compatibility: adding a new hook event is non-breaking. Existing hook configs that don't subscribe to PermissionDecision are unaffected.
  • Related but distinct: --permission-prompt-tool and the SDK canUseTool callback solve the replace the picker problem. This FR is the smaller observe the picker change — keep Claude Code's native UI as-is, just emit a signal when it resolves.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [FEATURE] Expose permission picker decisions (rule + option kind) to hooks