P1: Non-Interactive by Default
Definition
Every automation path MUST run without human input. A CLI tool that blocks on an interactive prompt is invisible to an agent — the agent hangs, the user sees nothing, and the operation times out silently.
Why Agents Need It
An agent calling a CLI cannot type. When the tool prompts for a confirmation or a credential, the agent's process stalls until timeout: no tokens recovered, no structured signal that interaction was requested, and no way to distinguish "waiting for input" from "still processing." Interactive prompts in automation paths are the single most common cause of agent-tool deadlock.
Requirements
MUST:
-
Every flag settable via environment variable. Use a falsey-value parser for booleans so that
TOOL_QUIET=0andTOOL_QUIET=falsecorrectly disable the flag rather than being treated as truthy non-empty strings. In Rust / clap:#[arg(long, env = "TOOL_QUIET", global = true, value_parser = FalseyValueParser::new())] quiet: bool, -
When
--no-interactiveis set, or when stdin is not a TTY, the tool does not enter any blocking-interactive surface — it uses defaults, reads from stdin, or exits with an actionable error. "Blocking-interactive surface" includes prompt library calls AND TUI session initialization. -
(Applies when: CLI authenticates against a remote service.) A headless authentication path. The canonical flag is
--no-browser, which triggers the OAuth 2.0 Device Authorization Grant (RFC 8628): the CLI prints a URL and a code; the user authorizes on another device. Agents cannot open browsers. Non-canonical alternatives (--device-code,--remote,--headless) are acceptable but should migrate toward--no-browser.
SHOULD:
- Auto-detect non-interactive context via TTY detection (
std::io::IsTerminalin Rust 1.70+,process.stdin.isTTYin Node,sys.stdout.isatty()in Python) and suppress prompts when stderr is not a terminal, even without an explicit--no-interactiveflag. - Document default values for prompted inputs in
--helpoutput so agents can pass them explicitly instead of accepting whatever default ships.
MAY:
- Offer rich interactive experiences — spinners, progress bars, multi-select menus — when a TTY is detected and
--no-interactiveis not set, provided the non-interactive path remains fully functional.
Scope
"Agent" in this specification means a process invoking the CLI as a subprocess. This spec's automated checks verify
behavior under non-TTY stdin. TTY-driving agents (tmux panes, ssh -t sandbox shells, expect automation, computer-use
desktop agents) are affected by the same MUSTs — but anc currently does not allocate a PTY during verification. Pass
verdicts for TTY-driving-agent scenarios are probable-but-not-verified; see /coverage for the gap.
Evidence
--no-interactiveflag in the CLI struct with an env-var binding.- Boolean env vars parsed with a falsey-value parser (not the default string parser).
- TTY guard wrapping every
dialoguer,inquire, or equivalent prompt call. --no-browserflag present on authenticated CLIs.env = "TOOL_..."attribute on every flag that takes user input.
Anti-Patterns
- Bare
dialoguer::Confirm::new().interact()with no TTY check and no--no-interactiveoverride — agents hang indefinitely. - Boolean environment variables parsed as plain strings, so
TOOL_QUIET=falseis truthy because the string is non-empty. stdin().read_line()in a code path reached during normal operation without a TTY check first.- Hard-coded credentials prompts with no env-var or config-file alternative.
- OAuth flow that unconditionally opens a browser with no headless escape hatch.
Measured by check IDs p1-non-interactive (behavioral) and p1-non-interactive-source (source). Run agentnative check --principle 1 . against your CLI to see both.