Skip to content

REQ-C-005: Interactive Commands Must Support --yes / --non-interactive

Tier: Command Contract | Priority: P0

Source: §10 Interactivity & TTY Requirements

Addresses: Severity: Critical / Token Spend: High / Time: Critical / Context: Low


Description

Any command that would prompt the user for confirmation or input in interactive mode MUST accept --yes (auto-confirm all prompts) and --non-interactive (fail immediately with exit code 4 if any prompt would be shown). The framework MUST register these flags automatically for any command that declares it uses interactive prompts. Command authors MUST declare prompt usage in registration metadata. A command declaring requires_person: true (REQ-C-036) is the one exception: its attestation exists so that a person, not the calling agent, confirms the command, and --yes does not answer it.

Acceptance Criteria

  • A command that prompts for confirmation in interactive mode accepts --yes and proceeds without prompting, except that a command with requires_person: true ignores --yes for its attestation and exits 4 with PERSON_REQUIRED off a terminal (REQ-C-036)
  • With --non-interactive, a command that would prompt exits with exit code 4 and a JSON error
  • The --yes flag is idempotent: passing it to a command that never prompts has no effect, except on a command whose confirm_flag is yes (REQ-O-048), where it confirms execution and the command runs instead of previewing
  • The --schema output for interactive commands includes interactive: true

Schema

Types: manifest-response.md

The interactive boolean and the standard --yes / --non-interactive flags appear in the command's schema entry.

{
  "interactive": {
    "type": "boolean",
    "description": "True if the command may prompt for user input in a TTY context"
  }
}

Wire Format

$ tool delete-account --user 42 --schema
{
  "command": "delete-account",
  "interactive": true,
  "flags": {
    "user":            { "type": "integer", "required": true,  "description": "User ID to delete" },
    "yes":             { "type": "boolean", "required": false, "default": false, "description": "Auto-confirm all prompts" },
    "non-interactive": { "type": "boolean", "required": false, "default": false, "description": "Fail immediately if a prompt would be shown" }
  },
  "exit_codes": {
    "0": { "name": "SUCCESS",      "description": "Account deleted",                     "retryable": false, "side_effects": "complete" },
    "4": { "name": "PRECONDITION", "description": "Prompt required but non-interactive mode is active", "retryable": false, "side_effects": "none" }
  }
}

Example

A command that would prompt for confirmation in interactive mode declares interactive: true at registration. The framework auto-registers --yes and --non-interactive.

register command "delete-account":
  interactive: true
  danger_level: destructive
  exit_codes:
    SUCCESS    (0): description: "Account deleted",                               retryable: false, side_effects: complete
    PRECONDITION(4): description: "Prompt required but non-interactive mode is active", retryable: false, side_effects: none

  execute(args, interactive_context):
    if not args.yes and interactive_context.is_tty():
      confirmed = prompt("Delete user {}? [y/N]".format(args.user))
      if not confirmed:
        return response(ok=False, exit=PRECONDITION)
    delete_user(args.user)
    return response(effect="deleted")

# agent call — no prompt, no hang:
# tool delete-account --user 42 --yes

Requirement Tier Relationship
REQ-F-009 F Provides: framework auto-detects non-TTY and suppresses prompts globally
REQ-C-002 C Composes: destructive commands are the primary case requiring interactive confirmation
REQ-C-004 C Composes: --dry-run is the preview step before the --yes-confirmed live run
REQ-O-021 O Extends: --confirm-destructive is a stronger opt-in variant for destructive confirmation
REQ-O-048 O Composes: a command whose confirm_flag is yes previews without --yes instead of prompting
REQ-C-036 C Specializes: a person-only attestation is the one prompt --yes does not answer