ax-check rule

AXC-C001: Command hangs without a terminal

A probe did not finish within the timeout with no TTY, which usually means it is waiting for input.

ax-check is a checker being prepared for release. This page documents the rule ahead of that release; see all 50 rules.

Severityerror
KindObserved. Depends on the program and environment at run time.
Modeax-check probe-cli
Applies toCommand-line programs, run as a subprocess
Pattern tagsnon-interactive, confirmation
Fix in one lineWhen stdin is not a TTY, never prompt: fail fast with an error that names the flag to pass instead.

A probe did not finish within the timeout when there was no terminal. This usually means the program is waiting for someone to type an answer. An agent has no keyboard, so the call hangs until something kills it.

What it checks

ax-check probe-cli runs your command with a small set of probes appended (by default --help and --version) with stdin closed (the default, --stdin ignore) and stdout and stderr connected to pipes. Each probe has a timeout of 10 seconds by default (change it with --timeout <ms>). When a probe does not finish in time, ax-check kills the process and reports one finding for that probe.

Why it matters

An agent runs commands through a harness. It cannot answer “Are you sure? [y/N]” or pick an item from a menu. A prompt that waits forever wastes the whole turn and may hold a lock or a connection open. The Command Line Interface Guidelines say: “If stdin is not an interactive terminal, skip prompting and just require those flags/args.” A fast error that names the missing flag is far better than a hang, because the agent can act on it.

How to fix

When stdin is not a TTY, never prompt. If a value is missing, fail fast with a non-zero exit code and an error that names the flag to pass. Offer --yes or --force for confirmations, and make sure the prompt code path is never reached without a TTY.

Example

Before

$ invoicer init </dev/null
Project name: 
^C
$ timeout 10 invoicer init </dev/null; echo $?
Project name: 
124

The program printed a prompt and waited. Exit status 124 comes from timeout, which killed it after 10 seconds.

After

$ invoicer init </dev/null; echo $?
error: --name is required when input is not a terminal.
Run: invoicer init --name <project-name>
2

A Node fix:

if (!process.stdin.isTTY && !args.name) {
  console.error('error: --name is required when input is not a terminal.');
  console.error('Run: invoicer init --name <project-name>');
  process.exit(2);
}

A Python fix:

if not sys.stdin.isatty() and not args.name:
    print("error: --name is required when input is not a terminal.", file=sys.stderr)
    print("Run: invoicer init --name <project-name>", file=sys.stderr)
    sys.exit(2)

How ax-check detects it

Every probe runs with no TTY and CI=1 set, in a temporary working directory, with HOME and the XDG directories pointing into a temporary directory (unless you pass --real-home). The environment is otherwise inherited, or reduced to PATH with --env-clear. The rule fires once for each probe that hit the timeout: by default help (and its -h fallback), version and no-color; with --probe, also unknown-subcommand, unknown-flag, missing-value, json and the help fallback; with --allow-bare, the bare command. The probe name appears in the finding, so you can see which invocation hung.

To reproduce by hand: timeout 10 invoicer init </dev/null; echo $?. On macOS, timeout comes from GNU coreutils (gtimeout). With --stdin pipe ax-check keeps an open, silent pipe instead of a closed stdin, which is stricter because a program that reads until end of file will then wait.

Known false positives: a command that is meant to run for a long time, such as a server or a file watcher, will time out by design. Point ax-check at a command that finishes, or raise --timeout. Silence the rule with --disable AXC-C001 when the hang is intended.

Safety note: This rule looks at every probe that ran. By default those are --help (and -h if it fails), --version, and --help with NO_COLOR=1. The error, json and help-word probes run only with --probe, and the bare command only with --allow-bare. probe-cli executes your program under your own OS account. The temporary working directory and HOME it uses are not a sandbox: the program can still read and write anything your account can, and use the network. Probe a CLI you have not reviewed only inside a container or a throwaway virtual machine. Run ax-check probe-cli --dry-run -- invoicer first to see the probes that would run, and see the “Probe safety” section of the ax-check README for what is and is not isolated.

Sources

  • Guideline: Command Line Interface Guidelines, clig.dev. https://clig.dev/ . States that if stdin is not an interactive terminal, a program should skip prompting and require flags or arguments instead.

Records in the AX evidence register that share a pattern tag with this rule. A shared tag means the record is about the same pattern, not that it tests this rule. Read the evidence class before the number.