Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

project.commands-run-non-interactively@2

A command runs with nobody at the keyboard

What it is

URI
majordomus://rule/project.commands-run-non-interactively@2
Kind
rule
Identity
project.commands-run-non-interactively@2
Path
.ai/repo/rules/project/commands-run-non-interactively.v2.md
Provenance
declared
Media type
text/markdown
As a capability
rule.project.commands-run-non-interactively@2

Front matter

{
  "id": "project.commands-run-non-interactively",
  "version": 2,
  "kind": "rule",
  "title": "A command runs with nobody at the keyboard",
  "description": "Where a non-interactive form of a command exists it is the one used — pagers disabled, confirmations passed as flags, stdin supplied explicitly — so that no step of any automated run can block waiting for a person who is not there.",
  "statement": "Where a non-interactive form exists it is the one used; a command that can block for input is not run unless the operator asked for an interactive session.",
  "status": "active",
  "class": "blocking",
  "depends_on": [
    "project.portable-shell@1",
    "project.execution-state-is-authoritative@1"
  ],
  "tags": [
    "shell",
    "process",
    "agents"
  ],
  "x-majordomus": {
    "tests": [
      "test/cases/08_no_forbidden_constructs.sh",
      "test/cases/122_liveness_gate.sh",
      "test/cases/121_liveness_doctrine.sh"
    ]
  }
}

Content

---
id: project.commands-run-non-interactively
version: 2
kind: rule
title: A command runs with nobody at the keyboard
description: Where a non-interactive form of a command exists it is the one used — pagers disabled, confirmations passed as flags, stdin supplied explicitly — so that no step of any automated run can block waiting for a person who is not there.
statement: Where a non-interactive form exists it is the one used; a command that can block for input is not run unless the operator asked for an interactive session.
status: active
class: blocking
depends_on: [project.portable-shell@1, project.execution-state-is-authoritative@1]
tags: [shell, process, agents]

x-majordomus:
  tests: [test/cases/08_no_forbidden_constructs.sh, test/cases/122_liveness_gate.sh, test/cases/121_liveness_doctrine.sh]
---

# Rationale

The workers in this repository are not people, and the commands they reach for were mostly
written for people. `git log` pipes itself into a pager that waits for `q`. `git rebase -i`
opens an editor. A package manager asks whether to proceed. Each of these is a small
courtesy to a human operator and a deadlock to everything else, because the program is not
broken and not slow — it is waiting, correctly, for input that will never come.

This failure is worse than a crash in a specific way: **it consumes the whole wait budget and
reports nothing.** A command that exits non-zero says what went wrong in a second. A command
blocked on a pager produces no output, no exit code and no diagnosis; from outside it is
indistinguishable from slow work, which is the ambiguity `project.every-wait-is-bounded`
exists to resolve and this rule exists to avoid creating in the first place.

The fix is not clever and does not need to be. Almost every such program has a documented
non-interactive form — an environment variable, a `--yes`, a `--no-pager`, a `--batch`, an
argument instead of a prompt. Choosing it costs nothing and removes an entire class of hang.

# Required behaviour

**The non-interactive form is the default choice.** Pagers are disabled (`PAGER=cat`,
`GIT_PAGER=cat`, `--no-pager`). Confirmations are supplied as explicit flags rather than
answered at a prompt. Where a command reads stdin, stdin is provided or closed deliberately
rather than left attached to a terminal that may or may not exist. Where a program honours
`CI`, `TERM=dumb` or an equivalent, set it.

**A command that can block for input is not started.** Interactive pagers and viewers, editors,
full-screen monitors, and REPLs are not part of an automated run. Neither is any command whose
documented behaviour includes waiting for a keypress. Where such a command is the only way to
do something, that is a finding, not a step.

**An interactive session is an explicit request.** The operator may ask for one, and then this
rule does not apply to what they asked for. Nothing else lifts it — in particular, "it will
probably be fine because the output is short" does not, because whether a pager engages depends
on the terminal, not on the output.

**Confirmation and interaction are not the same thing.** This rule requires that a
confirmation be passed as an argument, not that it be skipped. A destructive action still needs
the authority the repository requires for it; what changes is that the authority arrives as a
flag decided in advance rather than as a prompt answered by whoever happens to be watching.

# Failure behaviour

`test/cases/08_no_forbidden_constructs.sh` fails when anything that runs unattended — `scripts/`,
`lib/`, the suite, `bin/` and the CI definitions — starts one of the constructs named above at
command position: a pager at the end of a pipe, an editor, a full-screen monitor, a `read -p`
prompt, an interactive git mode, `npx` without `--yes`, an interactive login, `sudo` that may ask
(a hosted runner's sudo cannot, so workflows are exempt from that one), or a container given a
terminal. The finding names the construct, the file and the line. The scan is tested against
itself first: each construct is planted in a fixture and must be found, and the prose and
non-interactive forms beside it must not be.

`scripts/liveness-check` decides the other half over every tracked shell file: a `git log`,
`diff`, `show` or `blame` whose output is neither captured, piped, redirected nor `--no-pager`
pages to a terminal and stops on a keypress no agent sends, and is reported as a `pager-blocks`
finding. It is gated `always: true` in the `structure` job with its debt ratcheted in
`.ai/repo/liveness-baseline.txt`.

What neither scan can see stays with review: a program whose documented behaviour is to wait for
a keypress but whose name is not on the list, and a command a person runs by hand.

# Verification

`test/cases/08_no_forbidden_constructs.sh`, the mechanical half ADR 0039 placed there, and
`scripts/liveness-check`'s `pager-blocks` shape, mutation-tested by
`test/cases/122_liveness_gate.sh`. `test/cases/121_liveness_doctrine.sh` holds this rule's own
class and enforcement block.