Skip to content

feat: never block non-interactive callers on confirmation prompts - #28

Merged
ngjunsiang merged 1 commit into
mainfrom
feat/non-interactive-confirmation
Oct 4, 2026
Merged

ngjunsiang merged 1 commit into
mainfrom
feat/non-interactive-confirmation

Conversation

@nycomp

@nycomp nycomp commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Closes #27.

What

Destructive commands prompt for confirmation since #26 — right for humans, a landmine for agents/CI. This PR implements the non-interactive contract proposed in #27, following the gh / GIT_TERMINAL_PROMPT=0 pattern: the CLI never reads confirmation without a TTY.

  • New common.confirm_destructive(message, skip) replaces the four typer.confirm call sites (auth logout, client delete, client revoke, vault delete).
  • No TTY and no waiver → immediate exit 1:
    ✗ Refusing to proceed without confirmation (no TTY): delete client 'uid-x'. Re-run with -y, or set CAMPUS_ASSUME_YES=1 for non-interactive use.
    
  • CAMPUS_ASSUME_YES=1 (also true/yes) waives confirmation for the session; per-command --confirm/-y still takes precedence. Falsy values (0, false, empty) deliberately do not waive.
  • TTY behavior unchanged: interactive prompt, typer.Abort on decline.
  • README gains a "Non-interactive use (scripts, CI, agents)" section documenting the contract.

Verification

  • 146 tests pass (was 134; +12: refusal message/exit code with piped stdin, -y proceeds, env-var truthy/falsy spellings, TTY prompt still fires and propagates aborts, client-delete contract end to end). ruff clean.
  • Live (Windows, Git Bash): piped stdin → refusal with remedy, exit 1, no hang; CAMPUS_ASSUME_YES=1 → proceeds without the flag.

Known edge (documented, acceptable)

On Windows, redirecting from the NUL device (cmd < NUL) reports isatty() == True (character-device quirk), so that exotic edge keeps click's immediate EOF abort — a bare Aborted. with exit 1. Still no hang. Real agent harnesses and CI pipe stdin, which refuses correctly.

Notes for reviewers

  • logout keeps its confirmation (not treated as low-stakes) because it revokes tokens server-side, which can kill other live sessions — per the issue discussion.
  • Dry runs are unaffected: they short-circuit before any confirmation.
  • CI-only concern: none; GitHub Actions runners pipe stdin, so the refusal path (exit 1) is what CI would see if a test ever invoked a destructive command without a waiver — the updated tests pin that.

Destructive commands (since #26) prompt by default. For agents and CI
that prompt is a landmine: an open stdin hangs until timeout, a closed
one aborts opaquely with a bare 'Aborted.' Following the gh
'--yes required when not running interactively' and git
GIT_TERMINAL_PROMPT=0 patterns, the CLI now never attempts a
confirmation read without a TTY:

- new common.confirm_destructive(message, skip) replaces the four
  typer.confirm call sites (auth logout, client delete, client
  revoke, vault delete)
- no TTY and no waiver exits 1 immediately, naming -y and
  CAMPUS_ASSUME_YES as remedies
- CAMPUS_ASSUME_YES=1|true|yes waives confirmation for the session;
  the per-command --confirm/-y flag still takes precedence
- TTY behavior is unchanged: interactive prompt, abort on decline
- README documents the automation contract (closes #27)

Windows note: '< NUL' reports isatty()=True (character-device quirk),
so that exotic edge keeps click's immediate EOF abort — still no hang.
Real agent harnesses pipe stdin, which refuses correctly (verified).
@ngjunsiang
ngjunsiang merged commit 18eeca1 into main Oct 4, 2026
1 check passed
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.

Destructive commands block (or abort opaquely) when run non-interactively — need non-TTY refusal + CAMPUS_ASSUME_YES

2 participants