Skip to content

feat(tui): require consent before enabling auto-approve permissions - #133

Merged
quickbeard merged 3 commits into
mainfrom
auto-approve-consent
Jul 30, 2026
Merged

quickbeard merged 3 commits into
mainfrom
auto-approve-consent

Conversation

@quickbeard

@quickbeard quickbeard commented Jul 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Auto-approve (--auto) previously turned on silently — the only signal was the red auto badge in the prompt footer. This PR adds an explicit consent gate, shared by both entry points:

  • Launching with --auto (or the hidden --yolo / --dangerously-skip-permissions aliases) no longer enables auto-approve directly. On startup a DialogConfirm explains that permission requests not explicitly denied will be approved automatically and that this is dangerous (the word rendered in theme.error red, like the auto badge); the mode turns on only after the user confirms, followed by a warning toast. Declining or dismissing (esc) stays in manual mode.
  • The dialog opens on Cancel, so a stray return declines instead of granting auto-approve.
  • Switching into auto mode with shift+tab (or the palette command) goes through the same dialog and toast the first time. Consent is remembered for the rest of the run, so later toggles are silent; disabling is always instant.

The toast does not replay the dialog — it only has a few seconds on screen, so it restates the mode in one line: "Auto-approve is on — a dangerous mode where commands run and files change without asking."

Changes

  • packages/tui/src/context/permission.tsx: mode always starts "normal"; added a per-run consented flag + consent(); replaced toggle() with explicit set(mode) so there is no ungated path into auto mode.
  • packages/tui/src/app.tsx: shared enableAutoApprove() consent gate, an onMount hook for args.auto, and the permission.mode command now enables via the gate. The dialog and toast bodies are autoApproveMessage / autoApproveToast accessors that redden the risk word — passed unevaluated, since renderables can only be created during a render pass and the gate runs outside one.
  • packages/tui/src/ui/dialog-confirm.tsx: optional defaultChoice prop (+ 5th DialogConfirm.show arg) selecting the initially-active button; omitting it keeps the existing confirm-first default, so other callers are unaffected. message widened to accept an accessor and resolved at the render site.
  • packages/tui/src/ui/toast.tsx: same message widening, resolved by a message() helper in Toast(). Existing string callers still typecheck.
  • packages/tui/test/auto-approve-consent.view.test.tsx (new): boots the real app with args.auto against the fetch fixture and covers the prompt on startup, a bare return declining, confirming enabling auto mode with the warning toast, and consent being remembered across later toggles. The red word is asserted via captureSpans() — char frames carry no color.
  • DIVERGENCES.md row + CHANGELOG.md Unreleased entry per fork policy.

Headless codev run --auto is deliberately untouched — a consent prompt there would break scripted usage.

Testing

  • bun typecheck passes in packages/tui.
  • bun test test/auto-approve-consent.view.test.tsx — 3/3; also green alongside the neighboring view suites (configure-gate, custom-provider, interrupt).
  • Both behavioral guards were checked by temporarily reverting the source: the cancel-default test fails without defaultChoice: "cancel", and the color assertion fails when the word is not theme.error.

🤖 Generated with Claude Code

quickbeard and others added 3 commits July 29, 2026 21:39
Launching with --auto (or its hidden aliases) no longer enables
auto-approve silently: a confirm dialog explains the risk and the mode
turns on only after the user confirms, followed by a warning toast
reusing the same message. Enabling auto mode via shift+tab goes through
the same one-time gate; consent is remembered for the rest of the run,
so later toggles are silent. Declining or dismissing stays in manual
mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The consent dialog inherited DialogConfirm's confirm-first default, so a
stray return granted auto-approve. Add an opt-in `defaultChoice` prop and
start this dialog on cancel, name the risk as "dangerous" in the shared
message that both the dialog and the warning toast render, and cover the
gate with a view test: the prompt on `--auto`, return declining, and
consent being remembered for the rest of the run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The consent dialog and its toast now render the risk word in theme.error,
the same red as the `auto` badge, so the warning reads as a danger signal.
The message is passed as an accessor because renderables can only be
created during render and the gate runs outside it, which widens
DialogConfirm's and Toast's `message` to accept one.

The toast no longer replays the dialog: it restates the mode in a single
line, since it is only on screen for a few seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit 9cff077 into main Jul 30, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the auto-approve-consent branch July 30, 2026 01:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant