Four gaps between docs/cli.md and what an operator actually gets, found by building a real nightly job from the docs alone.
1. --help and docs/cli.md state different contracts for --dangerously-skip-approval
--help says:
Implies --yes, --max-risk high and --non-interactive unless you set a lower --max-risk, which still wins.
docs/cli.md says:
--yes may now auto-approve HIGH-risk steps. The cap moves from MEDIUM to HIGH.
The second reads as though --yes still has to be typed, and never mentions --non-interactive at all. That difference changes what a script must pass, and it changes what --step-by-step combined with the flag does. Whichever is right, the other has to move.
2. The flag gates and banners subcommands that approve nothing
$ SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT=1 sysknife audit export --dangerously-skip-approval
[]
(full UNATTENDED MODE banner on stderr)
$ sysknife doctor --dangerously-skip-approval # env var unset
sysknife: --dangerously-skip-approval was passed but SYSKNIFE_I_ACCEPT_UNATTENDED_ROOT is not set to 1.
$ echo $?
1
This is deliberate, and the reason is in the source: a flag that is sometimes silently ignored teaches people it is harmless. But docs/cli.md frames the whole section around plan execution, and the only hint is one line under Global flags saying all flags apply to every subcommand.
A wrapper that uniformly appends the flag to every invocation, which is a normal "always non-interactive" pattern, gets hard refusals on health checks and audit shipping. Say so in the Unattended mode section.
Stream separation is correct here: the banner is on stderr and stdout stays clean, so jq still parses. Only the scope is undocumented.
3. SYSKNIFE_DATABASE_PATH is missing, and SYSKNIFE_SOCKET does nothing for audit commands
$ grep -c SYSKNIFE_DATABASE_PATH docs/cli.md
0
developer-guide.md and the-audit-chain.md both document it as the way to point audit export and audit verify at a specific store, including a copied remote database. docs/cli.md's own Environment variables section never mentions it.
Worse, the reverse is implied. SYSKNIFE_SOCKET has its own entry warning about forwarded sockets, and audit verify --pubkey is described as the auditor path, so pointing SYSKNIFE_SOCKET at a particular daemon looks like it selects which trail you export. It does not. Audit commands never dial the daemon; they read a local file. In a fleet with several daemons that is a silent wrong answer.
4. --log-to is a stdout-only tee, and the doc's own example produces an empty file on failure
Running docs/cli.md's own Scripting and CI example verbatim, on a run that failed:
$ cat /tmp/.../nightly.log
# 0 bytes
Every diagnostic line goes to stderr: the provider fallback notice, the planning status, and the final error. --log-to tees stdout only. On the one occasion an operator needs the log, it is empty, and nothing in the section says to capture stderr separately.
Scope
All four are docs/cli.md, except #1 which needs someone to decide which of the two statements is true and correct the other.
Difficulty
easy. Four sections of one file, plus reading main.rs once to settle #1.
Getting started
CONTRIBUTING.md has the build and test commands. docs/cli.md is not in the CI markdownlint list, so check it with npx markdownlint-cli2 docs/cli.md before pushing. No CLA and no copyright waiver. The project is MIT.
Four gaps between
docs/cli.mdand what an operator actually gets, found by building a real nightly job from the docs alone.1.
--helpanddocs/cli.mdstate different contracts for--dangerously-skip-approval--helpsays:docs/cli.mdsays:The second reads as though
--yesstill has to be typed, and never mentions--non-interactiveat all. That difference changes what a script must pass, and it changes what--step-by-stepcombined with the flag does. Whichever is right, the other has to move.2. The flag gates and banners subcommands that approve nothing
This is deliberate, and the reason is in the source: a flag that is sometimes silently ignored teaches people it is harmless. But
docs/cli.mdframes the whole section around plan execution, and the only hint is one line under Global flags saying all flags apply to every subcommand.A wrapper that uniformly appends the flag to every invocation, which is a normal "always non-interactive" pattern, gets hard refusals on health checks and audit shipping. Say so in the Unattended mode section.
Stream separation is correct here: the banner is on stderr and stdout stays clean, so
jqstill parses. Only the scope is undocumented.3.
SYSKNIFE_DATABASE_PATHis missing, andSYSKNIFE_SOCKETdoes nothing for audit commandsdeveloper-guide.mdandthe-audit-chain.mdboth document it as the way to pointaudit exportandaudit verifyat a specific store, including a copied remote database.docs/cli.md's own Environment variables section never mentions it.Worse, the reverse is implied.
SYSKNIFE_SOCKEThas its own entry warning about forwarded sockets, andaudit verify --pubkeyis described as the auditor path, so pointingSYSKNIFE_SOCKETat a particular daemon looks like it selects which trail you export. It does not. Audit commands never dial the daemon; they read a local file. In a fleet with several daemons that is a silent wrong answer.4.
--log-tois a stdout-only tee, and the doc's own example produces an empty file on failureRunning
docs/cli.md's own Scripting and CI example verbatim, on a run that failed:Every diagnostic line goes to stderr: the provider fallback notice, the planning status, and the final error.
--log-totees stdout only. On the one occasion an operator needs the log, it is empty, and nothing in the section says to capture stderr separately.Scope
All four are
docs/cli.md, except #1 which needs someone to decide which of the two statements is true and correct the other.Difficulty
easy. Four sections of one file, plus readingmain.rsonce to settle #1.Getting started
CONTRIBUTING.md has the build and test commands.
docs/cli.mdis not in the CI markdownlint list, so check it withnpx markdownlint-cli2 docs/cli.mdbefore pushing. No CLA and no copyright waiver. The project is MIT.