Service audit, cli cards

Last updated

Checks one running service: its API, failure handling, limits, and security. This page holds the cards for the cli context. The Service audit page lists every card. Report each check as PASS, FAIL, or N/A with file:line evidence. A FAIL carries the card's severity. Any block FAIL means request changes. A should-fix or discuss FAIL goes in the review but does not block.

Verdict: request changes - change, function - 2 block, 1 should-fix FAIL, 1 PASS, 14 N/A
FAIL [block] chg-expand-contract - src/a.py:42 - renames the column and drops the old one in one migration
FAIL [block] svc-sqli-authn - src/db.py:88 - proven: builds the WHERE clause with an f-string from request.args
FAIL [should-fix] chg-one-concern - src/b.py:7 - a logging rewrite rides along with the bug fix
PASS chg-breaking-change-taxonomy - src/c.py:15 - no signature or return shape changes
N/A: 14 cards, applies_when does not hold

Filters

Concerns
Topics

Checklist

Back to filters

What the badges mean

Cards

svc-cli-destructive-dry-run

should-fixstablereliabilitymaintainability

Applies when: A command-line tool has a command that deletes, overwrites or sends something.

Rule. Give a destructive command a preview flag that changes nothing and a flag that skips the confirmation prompt.

Why. Without a preview, the first run is the real one. Without a bypass, a script cannot run the command at all, so people write wrappers that pipe "yes" into it.

FAIL
func main() {
	fmt.Printf("Delete %d backups? [y/N] ", len(old))
	var ans string
	fmt.Scanln(&ans) // no preview flag, no way to skip the prompt
	if ans == "y" {
		for _, b := range old {
			os.Remove(b)
		}
	}
}
PASS
dry := flag.Bool("dry-run", false, "list what would be deleted, delete nothing")
yes := flag.Bool("yes", false, "do not ask for confirmation")
flag.Parse()
for _, b := range old {
	fmt.Println(b)
}
if *dry {
	return
}
if !*yes && !confirm(fmt.Sprintf("Delete %d backups?", len(old))) {
	os.Exit(1)
}
for _, b := range old {
	if err := os.Remove(b); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

Counterpoint. "A bypass flag invites accidents." The prompt stays the default for people, and the flag has to be typed on purpose. Pair it with svc-cli-non-interactive.

Tool gate: CLI test that runs the command with the preview flag and checks nothing changed.

Backed by 8 organisations, backing unjudged:

People cited:

  • Command Line Interface Guidelines (unjudged): clig.dev/

svc-cli-exit-codes-and-streams

should-fixstablecorrectnessmaintainability

Applies when: The code ships a command-line tool that scripts or other programs may run.

Rule. Exit non-zero when a command fails, and write results to stdout and errors and progress to stderr.

Why. Scripts branch on the exit code and pipe stdout into the next program. A failure that exits 0 reads as success, and an error line on stdout lands in the next program's input. Add a --json flag for structured results, and keep prompts out of non-terminal runs (see svc-cli-non-interactive).

FAIL
if err := run(); err != nil {
	fmt.Println("error:", err) // error goes into the pipe
}
// main returns: exit status 0
PASS
if err := run(); err != nil {
	fmt.Fprintln(os.Stderr, "error:", err)
	os.Exit(1)
}

Counterpoint. "Some tools use exit codes for non-error results." Documented codes for states such as "differences found" are fine. The rule is that a failure never exits 0.

Tool gate: CLI test that runs a failing command and checks the exit code and that stdout is empty.

Backed by 7 organisations, backing unjudged:

People cited:

  • Command Line Interface Guidelines (unjudged): clig.dev/

svc-cli-non-interactive

should-fixstablemaintainabilityprocess

Applies when: The code ships a command-line tool that scripts or agents may run.

Rule. Let the caller turn every prompt off with a flag or an environment variable, so the command never waits on a terminal.

Why. A script or an agent has no one to answer a prompt, so the command hangs until a timeout kills it. When stdin is not a terminal, the best behaviour is to fail at once with an error that names the missing flag and shows an example call, so the caller fixes it in one step.

FAIL
env := flag.String("env", "", "target environment")
flag.Parse()
if *env == "" {
	fmt.Print("Choose environment: ")
	fmt.Scanln(env) // always prompts: no way to turn it off, hangs in CI
}
PASS
env := flag.String("env", "", "target environment")
noInput := flag.Bool("no-input", false, "never prompt")
flag.Parse()
if *env == "" {
	if *noInput || !term.IsTerminal(int(os.Stdin.Fd())) { // golang.org/x/term
		fmt.Fprintln(os.Stderr, "error: --env is required. Example: deploy --env staging")
		os.Exit(2)
	}
	fmt.Fprint(os.Stderr, "Choose environment: ")
	fmt.Scanln(env)
}

Counterpoint. "Prompts are friendlier for people." Keep them. The rule only asks that the caller can switch prompts off.

Tool gate: CLI test that runs every command with stdin closed.

Backed by 5 organisations, backing unjudged:

People cited:

  • Command Line Interface Guidelines (unjudged): clig.dev/