feat(chat): show why Claude asks for approval in Full access

HA
1h ago
+606-93

Links

Summary

  • The approval card now shows the reason Claude Code gives for a prompt, and says when it asked even though the chat is in Full access.
  • The native claude-code harness now shows AskUserQuestion as a question form instead of a yes/no "Allow this tool call?" prompt.
  • The Full access mode description changes from "Never asks before acting" to "Rarely asks before acting".

Why / Context

Claude Code still asks before some actions in bypassPermissions mode (safety checks, ask rules). It sends a reason, but we dropped it, so a prompt in Full access looked like the mode had failed. The mode picker also promised it would never ask.

How It Works

  • Protocol: approvalRequestSchema gets an optional reason. It is a looseObject, so released clients ignore it.
  • ACP (claude-acp): claude-agent-acp sends the reason only to JetBrains AIR clients, at _meta.jetbrains.air.permission.description (prefixed with Reason: ). We already advertise AIR in initialize, so airPermissionReason() reads it from there. codex-acp sends the same field without the prefix.
  • Native (claude-code): canUseTool passes decisionReason through, trimmed. AskUserQuestion now becomes a form with one field per question plus a <id>_custom "Other" field, the same shape claude-agent-acp builds, so QuestionForm renders it. Answers fold back into the tool input the way Claude Code's call() reads them: multi-select picks are comma-joined and quoted, and text typed beside a single-select pick goes into annotations[question].notes. Skip lets the tool run with no answers; cancel interrupts the turn.
  • Desktop: a new SessionModeProvider gives ApprovalRow the chat's mode. A new ApprovalReason box shows the reason. In an unrestricted mode it adds the heading "Still asks in Full access mode", and falls back to a generic note when there is no reason. It shows only on pending option cards, never on forms or answered cards.

Manual QA Checklist

Not done yet: this was written in a cloud workspace that can't run Electron.

  • Full access Claude chat: a prompt Claude still raises (for example a long $(...) chain that sends a login code) shows the reason box with "Still asks in Full access mode"
  • "Ask for approval" mode: a prompt shows the reason without the heading, and looks the same as before when there is no reason
  • Old claude-code chat: AskUserQuestion renders as a question form; picks, "Other" text and Skip all reach Claude
  • Mode picker shows "Rarely asks before acting" for Full access
  • Answered and expired approval cards look the same as before

Testing

  • bun test in packages/chat-runtime (506 pass, 2 skip), packages/chat (120), apps/desktop src/renderer/routes/_authenticated (2536), packages/host-service src/chat-v3 (100)
  • New tests: AIR reason parsing, ACP approval reason, native decisionReason, native AskUserQuestion form round trip, answer folding (multi-select quoting, single-select notes)
  • bunx tsc --noEmit in packages/chat-runtime and apps/desktop
  • bunx biome check, bun run check:i18n (2 new strings plus the changed mode description, translated in all 17 locales)
  • Not run locally: the repo-wide bun run typecheck / bun run lint / sherif; CI covers those.

Known Limitations

  • claude-agent-acp's code calls the reason field temporary diagnostic text, so an adapter bump may change or drop it. The card then falls back to the generic note.
  • Mobile (ChatRowView) does not show the reason yet.
  • The note follows the chat's current mode, not the mode when Claude asked.
  • Agents other than Claude that ask a question as an options prompt, rather than a form, still get the generic Full access note.

Summary by cubic

Shows the reason Claude Code gives when it asks for approval, so prompts in Full access no longer look like the mode failed. Claude Code still asks before some actions in bypassPermissions mode (safety checks, ask rules), and the card previously dropped the reason. The native claude-code harness now renders AskUserQuestion as a question form instead of a yes/no "Allow this tool call?" prompt.

  • Approval items carry an optional reason: the ACP adapter reads it from _meta.jetbrains.air.permission.description, and the native harness passes decisionReason through.
  • The desktop approval card shows the reason with a "Still asks in Full access mode" heading in unrestricted modes, or a generic note when no reason was sent.
  • The Full access mode description now reads "Rarely asks before acting".
  • AskUserQuestion forms get one field per question plus an "Other" text field; answers fold back into the tool input, and Skip lets the tool run with no answers.

Summary by CodeRabbit

  • New Features
    • Approval prompts can now explain why an action needs permission, including prompts that remain in Full access mode.
    • Claude’s question requests are presented as interactive forms, with submitted answers passed back to the agent.
  • Updates
    • Full access is now described as rarely asking before acting, rather than never asking. Approval guidance is available in supported languages.