Skip to content

Clarify 'Always allow' scope on the approval card #264

Description

@germanescobar

Context

Today the approval card on a session offers three buttons: Allow once, Always allow, Deny. The label Always allow reads as a global, persistent allow list, but the underlying rule is actually session-scoped on the agent side:

  • Claude Code: the rule is a permissionUpdates entry with destination: "session" attached to one Claude session id (server/lib/agents.ts, buildClaudeApprovalResponse). It dies with that session.
  • Codex: the rule is acceptForSession inside one live codex app-server thread runtime (server/lib/codex-app-server.ts, mapApprovalDecision). It dies with the thread.

Because Controller keeps Claude and Codex sessions alive across turns (and across user visits to the same conversation), in practice these rules can live as long as the conversation does — but they still don't leak across conversations or worktrees. A "Reset" affordance in Settings (#259, PR #260) was the wrong surface for this: Codex's app-server manager is process-global rather than worktree-scoped, so the copy couldn't honestly claim a worktree scope, and the action is easy to regret. #260 is being closed in favor of fixing this at the source: the approval card itself.

Goal

Make the scope of "Always allow" obvious at the moment the user clicks it, so they don't grant it thinking they're adding a permanent global rule.

Proposed changes

All in client/src/pages/SessionView.tsx, inside ToolApprovalBlock and the tool_approval_response renderer.

  1. Rename the button from Always allow to Allow for this session. Drop the parenthetical — the new label stands on its own.

  2. Add a tooltip / help line under the button row explaining what "session" means in this product. A muted one-liner under the three buttons:

    "Allow for this session = same conversation. Won't ask again for similar commands here. Closed conversations forget it."

    Keep it short; if we add it to every approval card we should not blow up the visual weight — text-xs text-muted-foreground under the button row.

  3. Update the post-decision summary so the user can see what just happened. Today the tool_approval_response event renders a centered divider with one of: Approved, Approved (always allow), Denied. Change it to:

    • Allowed this once for allow_once
    • Allowed for this session for always_allow, and where the agent's suggestions let us, append the rule that was actually stored — e.g. Allowed for this session — Edit on src/api.ts. If we don't have the structured rule handy in data, fall back to Allowed for this session (no append) so we never show wrong info.
    • Denied unchanged.

    The per-file/per-command suffix is the "Edit has been approved for this file" idea the original issue hinted at — only render it when we have a real rule to show.

Acceptance criteria

  • Approval card button reads Allow for this session instead of Always allow.
  • A short scope explainer is visible on every approval card (button row + muted line), explaining that "session" = this conversation.
  • The post-decision summary line reflects the new labels and, when structured data is available, names the actual rule that was stored.
  • No changes to the decision enum on the wire (allow_once / always_allow / deny stays as-is in client/src/api.ts and the server handlers).
  • No changes to plan-mode flow (Approve & implement / Keep planning already says what it does).

Out of scope

  • A Settings-level Reset / Revoke UI (Revoke / reset per-session 'Always allow' permissions #259, closed).
  • Per-rule granular revocation (revoke one specific path, keep others).
  • Persisting "Always allow" rules across sessions (this is by design on the agent side).
  • Renaming the always_allow decision value used internally — it's already a fine wire name; only the user-facing label changes.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions