On this page

Keep a command's error output

Every error a Wisent product gives names what failed, why, and how to repair it. A command whose stderr was thrown away fails with nothing but its exit status, so the error built from it can only say that something failed. Every stado quality check of a product that declares its release (.wisent-release.json), including the pre-push check (--pre-push) and the check of one revision (--revision), therefore reads the lines each checked revision adds against the published main (every line when the checkout has no origin) and refuses a line that throws stderr away without saying why. One check judges this rule, in every product, before any gate runs.

The added line Example
sends stderr to /dev/null ls /nowhere 2>/dev/null, 2> /dev/null
sends both outputs there &>/dev/null, >/dev/null 2>&1
closes stderr 2>&-
gives a Rust child process a null stderr .stderr(Stdio::null())

Which lines are read

Only lines that run on a host are read. Prose (.md, .mdx) and the top-level tests, docs and .github directories are not: the same files the check of a product's reach on other services reads. A test holds these forms on purpose, to prove the check refuses them.

A line that is a comment as a whole runs no command, so a comment that names a form, as this page does, is not refused; it is read only as the reason heading the lines below it. Such a line starts with //, with /* or a * continuation of it (Rust, Swift, JavaScript, C), or with # (shell, Python, TOML, YAML), except a Rust attribute (#[, #![) and a script's interpreter line (#!), which are code. A comment after a command on the same line does not make the command a comment: ls missing 2>/dev/null # quiet is refused.

Lines a revision does not add are not judged, so an older line is repaired when its file is next changed for it. On a line it does add, only a discard the revision adds is judged: one whose command text — the discard and the command before it, back to the nearest ;, &, |, backtick, quote or parenthesis — the file holds more often than at the published base. A line rewritten around a discard its base already held is the base's: a generated one-line search index that quotes ls /nowhere 2>/dev/null from a page is rewritten whenever any sentence of that page changes, and a JSON file cannot carry the comment that would explain it. A discard that is new, or a further copy of one the base held, is refused on its line.

The refusal

The check refuses as stado.quality.check.discards-unexplained and names every line as <path>:<line>: <text>, the lines joined by ; :

Error: checking that <revision> of <checkout> says why each stderr it discards is expected: <revision> of <checkout> adds lines that discard stderr without saying why: <N> line(s); observed: probe.sh:2: ls /nowhere 2>/dev/null; …
fix: report the failure such a line hides (capture its stderr and name the command), or, where that failure is the expected answer, name it and why in a `# stderr: <why>` comment (`// stderr: <why>` in Rust) on that line or heading its run of discarding lines
cause: stado.quality.check.discards-unexplained; `stado errors show stado.quality.check` lists every way it fails

How to repair it

Keep the command's stderr and carry its words into the error the program gives: in a shell program a host runs, capture it (out=$(cmd 2>&1)) and print it on the line that reports the failure; in a program that starts a child, read its stderr and put it in the error.

A command whose failure is the expected answer, such as launchctl print of a unit that is not loaded or a command -v probe whose only answer is its exit status, names that failure and why in a comment of the language the line is written in: # stderr: <why> (// stderr: <why> in Rust), on the same line or heading the run of discarding lines and comments it covers. stderr discarded: <why> is the same note. The words alone, without a reason after them, do not pass the line.

# stderr: command -v answers by its status alone
command -v rsync >/dev/null 2>&1 || echo "rsync is not installed"

A failure let pass

The same check holds a shell script (a .sh, .bash or .zsh file, or one whose first line is a #! naming a shell) to the rule for a failure it lets pass whole: || true, || : and set +e go on as if the command had answered. A Swift source is held to it for try?, which reads a failure as nil, an empty catch {}, which drops it, and .localizedDescription, which keeps only its words. Such a line names the failure it lets pass and why in a # failure: <why> comment (// failure: <why> in Swift) on the line or heading its run, or the revision is refused as stado.quality.check.failures-dropped-unexplained. Rust is not read for these: its forms are counted by clippy (stado-rs/clippy.toml). A revision with both kinds is refused once, the dropped failures said after the discards.

Source: this website