Found while fixing issue #82. Filing rather
than fixing drive-by: #82 is about the logger's sink, and this is a
different writer on a different code path.
Problem
internal/cli/entry.go:20-27 prints the startup banner to stdout before
cobra parses anything:
bannerSuppressedInArgs recognizes only --quiet, -q and --cron.
It does not recognize --json, so every --json subcommand emits two
banner lines and a blank line onto stdout ahead of its document.
--json paths that pass opts.JSON as extraQuiet to runVaultikApp
(e.g. snapshot remove) do not help: that calls UI.SetQuiet(true) in
an fx OnStart hook, which runs long after Entry has already printed
the banner.
Reproduction
With a config at any permissions and the binary from main:
$ vaultik --config /tmp/demo/config.yml snapshot list --json
vaultik dev-... by Jeffrey Paul <sneak@sneak.berlin> (commit ..., built on ...) starting up at ....
https://sneak.berlin/go/vaultik
[]
The workaround is -q, which suppresses the banner as a side effect —
but a machine consumer should not have to know that, and --json
already communicates the intent.
Affected
snapshot list --json, snapshot verify --json, snapshot remove --json, prune --json, remote info --json.
Expected
--json implies the banner is suppressed. stdout carries the document
and nothing else, which is the same contract issue #82 established for
the logger.
Notes
Once #82 lands, the logger is no longer a source of stdout
contamination, so the banner is the remaining one.
bannerSuppressedInArgs already scans raw os.Args for flags because
the banner precedes cobra parsing; adding --json there is consistent
with how --quiet/--cron are handled, though it does mean a
subcommand-level flag is matched anywhere in the argument vector.
Whether that is acceptable or whether the banner should instead move
after parsing is the design question this issue needs answered.
A regression test should assert that stdout from a --json
invocation parses as exactly one JSON document. The existing TestListSnapshots_JSONStdoutIsOnlyTheDocument covers the library
call and cannot see Entry, so this one belongs in internal/cli.
Found while fixing
[issue #82](https://git.eeqj.de/sneak/vaultik/issues/82). Filing rather
than fixing drive-by: #82 is about the logger's sink, and this is a
different writer on a different code path.
## Problem
`internal/cli/entry.go:20-27` prints the startup banner to stdout before
cobra parses anything:
```go
if !bannerSuppressedInArgs(os.Args[1:]) {
writeStartupBanner(ui.New(os.Stdout), time.Now().UTC(), short)
}
```
`bannerSuppressedInArgs` recognizes only `--quiet`, `-q` and `--cron`.
It does not recognize `--json`, so every `--json` subcommand emits two
banner lines and a blank line onto stdout ahead of its document.
`--json` paths that pass `opts.JSON` as `extraQuiet` to `runVaultikApp`
(e.g. `snapshot remove`) do not help: that calls `UI.SetQuiet(true)` in
an fx `OnStart` hook, which runs long after `Entry` has already printed
the banner.
## Reproduction
With a config at any permissions and the binary from `main`:
```
$ vaultik --config /tmp/demo/config.yml snapshot list --json
vaultik dev-... by Jeffrey Paul <sneak@sneak.berlin> (commit ..., built on ...) starting up at ....
https://sneak.berlin/go/vaultik
[]
```
```
$ vaultik --config /tmp/demo/config.yml snapshot list --json | python3 -c 'import json,sys; json.load(sys.stdin)'
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```
The workaround is `-q`, which suppresses the banner as a side effect —
but a machine consumer should not have to know that, and `--json`
already communicates the intent.
## Affected
`snapshot list --json`, `snapshot verify --json`, `snapshot remove
--json`, `prune --json`, `remote info --json`.
## Expected
`--json` implies the banner is suppressed. stdout carries the document
and nothing else, which is the same contract
[issue #82](https://git.eeqj.de/sneak/vaultik/issues/82) established for
the logger.
## Notes
- Once #82 lands, the logger is no longer a source of stdout
contamination, so the banner is the remaining one.
- `bannerSuppressedInArgs` already scans raw `os.Args` for flags because
the banner precedes cobra parsing; adding `--json` there is consistent
with how `--quiet`/`--cron` are handled, though it does mean a
subcommand-level flag is matched anywhere in the argument vector.
Whether that is acceptable or whether the banner should instead move
after parsing is the design question this issue needs answered.
- A regression test should assert that stdout from a `--json`
invocation parses as exactly one JSON document. The existing
`TestListSnapshots_JSONStdoutIsOnlyTheDocument` covers the library
call and cannot see `Entry`, so this one belongs in `internal/cli`.
Scope addition — one more item lands with this, from the PR #107 review.
AGENTS.md policy 9 (lines 86-87) is now false. It states the log
format is keyed on stdout's TTY-ness. As of #82 the logger writes to
stderr and the format choice tests os.Stderr. The rule's operative
content is preserved — structured output when the log stream is not a
terminal — but the stream it names is wrong.
Fix it in the same change as the banner, since both are about which
stream carries what. One-line amendment: key the wording on the log
stream rather than stdout.
Worth doing rather than deferring: this repo has now corrected false
claims in snapshot.go's manifest comment (#81), the Vaultik.UI --cron comment (#84), TTYHandler's WithAttrs doc (#97), and the --cron flag help (#87). A rules file that misdescribes the code is the
same defect in the place agents are most likely to trust it.
Revised definition of done
--json suppresses the startup banner; stdout carries the document
and nothing else.
AGENTS.md policy 9 names the log stream, not stdout.
A regression test in internal/cli asserting a --json invocation's
stdout parses as exactly one JSON document — the existing TestListSnapshots_JSONStdoutIsOnlyTheDocument covers the library
call and cannot see Entry.
The design question in the original body is answered explicitly:
either bannerSuppressedInArgs scans raw os.Args for --json as it
already does for --quiet/--cron (accepting that a subcommand-level
flag is matched anywhere in the vector), or the banner moves after
parsing. Say which and why.
script/cibuild exits 0.
Two smaller observations from the same review, fix if cheap:
Vaultik.Stderr now has zero production writers and env.stderr zero
readers. vaultik.go documents keeping it deliberately — confirm that
still reads true, or remove it.
bytesAttrKey's human-readable byte formatting does not survive
grouping (x.bytes no longer matches the key check). Unreachable
today; worth a comment if not a fix.
Scope addition — one more item lands with this, from the
[PR #107](https://git.eeqj.de/sneak/vaultik/pulls/107) review.
**`AGENTS.md` policy 9 (lines 86-87) is now false.** It states the log
format is keyed on **stdout**'s TTY-ness. As of #82 the logger writes to
stderr and the format choice tests `os.Stderr`. The rule's operative
content is preserved — structured output when the log stream is not a
terminal — but the stream it names is wrong.
Fix it in the same change as the banner, since both are about which
stream carries what. One-line amendment: key the wording on the **log
stream** rather than stdout.
Worth doing rather than deferring: this repo has now corrected false
claims in `snapshot.go`'s manifest comment (#81), the `Vaultik.UI`
`--cron` comment (#84), `TTYHandler`'s `WithAttrs` doc (#97), and the
`--cron` flag help (#87). A rules file that misdescribes the code is the
same defect in the place agents are most likely to trust it.
## Revised definition of done
1. `--json` suppresses the startup banner; stdout carries the document
and nothing else.
2. `AGENTS.md` policy 9 names the log stream, not stdout.
3. A regression test in `internal/cli` asserting a `--json` invocation's
stdout parses as exactly one JSON document — the existing
`TestListSnapshots_JSONStdoutIsOnlyTheDocument` covers the library
call and cannot see `Entry`.
4. The design question in the original body is answered explicitly:
either `bannerSuppressedInArgs` scans raw `os.Args` for `--json` as it
already does for `--quiet`/`--cron` (accepting that a subcommand-level
flag is matched anywhere in the vector), or the banner moves after
parsing. Say which and why.
5. `script/cibuild` exits 0.
Two smaller observations from the same review, fix if cheap:
- `Vaultik.Stderr` now has zero production writers and `env.stderr` zero
readers. `vaultik.go` documents keeping it deliberately — confirm that
still reads true, or remove it.
- `bytesAttrKey`'s human-readable byte formatting does not survive
grouping (`x.bytes` no longer matches the key check). Unreachable
today; worth a comment if not a fix.
Implementation plan, on branch fix-json-banner off main.
1. The design question — extend bannerSuppressedInArgs, do not move the banner.
Moving the banner after parsing costs more than it buys. root.go
already documents why it is printed first: it must appear even when
cobra rejects the arguments (requires at least 2 arg(s)) and on --help. After parsing there is no single place that runs on all of
those paths, so "after parsing" means reimplementing the banner in
several handlers, or accepting that it disappears from the failure
paths where a human most wants to know which build they just ran.
The stated objection to the scan is that --json is a subcommand flag
matched anywhere in the vector. That objection applies equally to --cron, which is already in the list and is also a subcommand flag —
it exists only on snapshot create
(internal/cli/snapshot.go:137). So this adds no new class of
imprecision, only another instance of one the code already accepts.
And the two failure directions are not symmetric: a false positive
loses a decorative banner, a false negative corrupts a document on
stdout. The scan should err toward suppression, and does.
2. --json and --json= join --quiet/-q/--cron in the scan. --json=false will suppress the banner, exactly as --quiet=false
already does. Making the value parse honest for --json alone would
make it inconsistent with its two neighbours, and fixing all three is a
behaviour change nobody asked for; the doc comment states the bias
instead.
3. Tests, in internal/cli. Two layers:
TestEntryJSONStdoutIsExactlyOneDocument runs Entry itself with a
real argument vector over the process's real stdout descriptor, all
the way through cobra and fx to the document, and asserts the capture
decodes as exactly one JSON value with nothing after it. It is
hermetic because storage_url: file:// is exempt from the S3
credential checks and snapshot list treats a destination store with
no metadata/ as an empty list rather than a failure — so the
end-to-end path needs no network and no credentials.
A table over the argument vectors of all five --json commands
(plus --json before the subcommand, --json=true, and a
flag-with-value ahead of it), and a paired test asserting the banner
is still printed without a suppressing flag, so the first test cannot
be satisfied by deleting the banner.
4. AGENTS.md policy 9 re-worded to key on the log stream rather
than stdout, preserving the operative rule.
5. The two smaller observations.
Vaultik.Stderr: kept, comment amended to say outright that nothing
writes to it today, so the next reader does not go looking for the
writer. listEnv.stderr in snapshot_list_test.go is genuinely dead
— assigned, never read, because those tests capture the process's
stderr instead — and is removed.
bytesAttrKey: fixed rather than commented, since the fix is three
lines. The key reaching writeAttr is group-qualified, so the match
is made against the final dot-separated segment. Covered by a test
over both the grouped and ungrouped renderings.
Verification: script/cibuild, plus vaultik snapshot list --json | jq .
against a real config with no other flags, and the same for snapshot verify, snapshot remove, prune and remote info. The new test will
be confirmed to fail against the unfixed bannerSuppressedInArgs by
reverting it, not by assumption.
Implementation plan, on branch `fix-json-banner` off `main`.
**1. The design question — extend `bannerSuppressedInArgs`, do not move the banner.**
Moving the banner after parsing costs more than it buys. `root.go`
already documents why it is printed first: it must appear even when
cobra rejects the arguments (`requires at least 2 arg(s)`) and on
`--help`. After parsing there is no single place that runs on all of
those paths, so "after parsing" means reimplementing the banner in
several handlers, or accepting that it disappears from the failure
paths where a human most wants to know which build they just ran.
The stated objection to the scan is that `--json` is a subcommand flag
matched anywhere in the vector. That objection applies equally to
`--cron`, which is already in the list and is also a subcommand flag —
it exists only on `snapshot create`
(`internal/cli/snapshot.go:137`). So this adds no new class of
imprecision, only another instance of one the code already accepts.
And the two failure directions are not symmetric: a false positive
loses a decorative banner, a false negative corrupts a document on
stdout. The scan should err toward suppression, and does.
**2. `--json` and `--json=` join `--quiet`/`-q`/`--cron` in the scan.**
`--json=false` will suppress the banner, exactly as `--quiet=false`
already does. Making the value parse honest for `--json` alone would
make it inconsistent with its two neighbours, and fixing all three is a
behaviour change nobody asked for; the doc comment states the bias
instead.
**3. Tests, in `internal/cli`.** Two layers:
- `TestEntryJSONStdoutIsExactlyOneDocument` runs `Entry` itself with a
real argument vector over the process's real stdout descriptor, all
the way through cobra and fx to the document, and asserts the capture
decodes as exactly one JSON value with nothing after it. It is
hermetic because `storage_url: file://` is exempt from the S3
credential checks and `snapshot list` treats a destination store with
no `metadata/` as an empty list rather than a failure — so the
end-to-end path needs no network and no credentials.
- A table over the argument vectors of all five `--json` commands
(plus `--json` before the subcommand, `--json=true`, and a
flag-with-value ahead of it), and a paired test asserting the banner
is still printed without a suppressing flag, so the first test cannot
be satisfied by deleting the banner.
**4. `AGENTS.md` policy 9** re-worded to key on the log stream rather
than stdout, preserving the operative rule.
**5. The two smaller observations.**
- `Vaultik.Stderr`: kept, comment amended to say outright that nothing
writes to it today, so the next reader does not go looking for the
writer. `listEnv.stderr` in `snapshot_list_test.go` is genuinely dead
— assigned, never read, because those tests capture the process's
stderr instead — and is removed.
- `bytesAttrKey`: fixed rather than commented, since the fix is three
lines. The key reaching `writeAttr` is group-qualified, so the match
is made against the final dot-separated segment. Covered by a test
over both the grouped and ungrouped renderings.
Verification: `script/cibuild`, plus `vaultik snapshot list --json | jq .`
against a real config with no other flags, and the same for `snapshot
verify`, `snapshot remove`, `prune` and `remote info`. The new test will
be confirmed to fail against the unfixed `bannerSuppressedInArgs` by
reverting it, not by assumption.
Implemented in PR #109 (branch fix-json-banner, one commit).
Built.--json and --json= join --quiet/-q/--cron in bannerSuppressedInArgs. The design question is answered in favour of
extending the raw-argv scan: the banner is printed before parsing
deliberately, so that it survives cobra rejecting the arguments and --help, and after parsing no single place covers those paths. The
subcommand-vs-persistent distinction does not decide it, because --cron is already in the list and is also a subcommand flag — it
exists only on snapshot create. Full reasoning in the PR body. AGENTS.md policy 9 now names the log stream. bytesAttrKey matches on
the final dot-separated segment, so byte formatting survives grouping. Vaultik.Stderr kept with the comment amended to say outright that
nothing writes to it; listEnv.stderr removed as dead.
Verified. The three new internal/cli tests were confirmed to fail
against the unfixed scan by reverting it, not by assumption — including TestEntryJSONStdoutIsExactlyOneDocument, which runs Entry itself over
the process's real stdout descriptor through cobra and fx to the
document. End to end against a real config and a real snapshot, with no
other flags and no -q: snapshot list --json | jq ., snapshot verify --json, snapshot remove --json and remote info --json all exit 0. script/cibuild exit 0, 15 ok
packages, 0 (cached), 134s.
One item of the definition of done is not met, deliberately. prune --json | jq still fails, for a reason that is not the banner: pruneLocalSnapshots writes three lines of prose to stdout with no --json awareness, it reproduces identically on main before this
change, and -q never suppressed it either. Filed as issue #108 rather than
fixed here — a different writer on a different code path, the same
reason this issue was filed out of issue #82 instead of
being folded into it. It also needs a decision this issue does not
contain: whether those lines belong in the document, on the logger, or
suppressed outright.
Implemented in
[PR #109](https://git.eeqj.de/sneak/vaultik/pulls/109) (branch
`fix-json-banner`, one commit).
**Built.** `--json` and `--json=` join `--quiet`/`-q`/`--cron` in
`bannerSuppressedInArgs`. The design question is answered in favour of
extending the raw-argv scan: the banner is printed before parsing
deliberately, so that it survives cobra rejecting the arguments and
`--help`, and after parsing no single place covers those paths. The
subcommand-vs-persistent distinction does not decide it, because
`--cron` is already in the list and is also a subcommand flag — it
exists only on `snapshot create`. Full reasoning in the PR body.
`AGENTS.md` policy 9 now names the log stream. `bytesAttrKey` matches on
the final dot-separated segment, so byte formatting survives grouping.
`Vaultik.Stderr` kept with the comment amended to say outright that
nothing writes to it; `listEnv.stderr` removed as dead.
**Verified.** The three new `internal/cli` tests were confirmed to fail
against the unfixed scan by reverting it, not by assumption — including
`TestEntryJSONStdoutIsExactlyOneDocument`, which runs `Entry` itself over
the process's real stdout descriptor through cobra and fx to the
document. End to end against a real config and a real snapshot, with no
other flags and no `-q`: `snapshot list --json | jq .`,
`snapshot verify --json`, `snapshot remove --json` and
`remote info --json` all exit 0. `script/cibuild` exit 0, 15 `ok`
packages, 0 `(cached)`, 134s.
**One item of the definition of done is not met, deliberately.**
`prune --json | jq` still fails, for a reason that is not the banner:
`pruneLocalSnapshots` writes three lines of prose to stdout with no
`--json` awareness, it reproduces identically on `main` before this
change, and `-q` never suppressed it either. Filed as
[issue #108](https://git.eeqj.de/sneak/vaultik/issues/108) rather than
fixed here — a different writer on a different code path, the same
reason this issue was filed out of
[issue #82](https://git.eeqj.de/sneak/vaultik/issues/82) instead of
being folded into it. It also needs a decision this issue does not
contain: whether those lines belong in the document, on the logger, or
suppressed outright.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while fixing
issue #82. Filing rather
than fixing drive-by: #82 is about the logger's sink, and this is a
different writer on a different code path.
Problem
internal/cli/entry.go:20-27prints the startup banner to stdout beforecobra parses anything:
bannerSuppressedInArgsrecognizes only--quiet,-qand--cron.It does not recognize
--json, so every--jsonsubcommand emits twobanner lines and a blank line onto stdout ahead of its document.
--jsonpaths that passopts.JSONasextraQuiettorunVaultikApp(e.g.
snapshot remove) do not help: that callsUI.SetQuiet(true)inan fx
OnStarthook, which runs long afterEntryhas already printedthe banner.
Reproduction
With a config at any permissions and the binary from
main:The workaround is
-q, which suppresses the banner as a side effect —but a machine consumer should not have to know that, and
--jsonalready communicates the intent.
Affected
snapshot list --json,snapshot verify --json,snapshot remove --json,prune --json,remote info --json.Expected
--jsonimplies the banner is suppressed. stdout carries the documentand nothing else, which is the same contract
issue #82 established for
the logger.
Notes
contamination, so the banner is the remaining one.
bannerSuppressedInArgsalready scans rawos.Argsfor flags becausethe banner precedes cobra parsing; adding
--jsonthere is consistentwith how
--quiet/--cronare handled, though it does mean asubcommand-level flag is matched anywhere in the argument vector.
Whether that is acceptable or whether the banner should instead move
after parsing is the design question this issue needs answered.
--jsoninvocation parses as exactly one JSON document. The existing
TestListSnapshots_JSONStdoutIsOnlyTheDocumentcovers the librarycall and cannot see
Entry, so this one belongs ininternal/cli.Scope addition — one more item lands with this, from the
PR #107 review.
AGENTS.mdpolicy 9 (lines 86-87) is now false. It states the logformat is keyed on stdout's TTY-ness. As of #82 the logger writes to
stderr and the format choice tests
os.Stderr. The rule's operativecontent is preserved — structured output when the log stream is not a
terminal — but the stream it names is wrong.
Fix it in the same change as the banner, since both are about which
stream carries what. One-line amendment: key the wording on the log
stream rather than stdout.
Worth doing rather than deferring: this repo has now corrected false
claims in
snapshot.go's manifest comment (#81), theVaultik.UI--croncomment (#84),TTYHandler'sWithAttrsdoc (#97), and the--cronflag help (#87). A rules file that misdescribes the code is thesame defect in the place agents are most likely to trust it.
Revised definition of done
--jsonsuppresses the startup banner; stdout carries the documentand nothing else.
AGENTS.mdpolicy 9 names the log stream, not stdout.internal/cliasserting a--jsoninvocation'sstdout parses as exactly one JSON document — the existing
TestListSnapshots_JSONStdoutIsOnlyTheDocumentcovers the librarycall and cannot see
Entry.either
bannerSuppressedInArgsscans rawos.Argsfor--jsonas italready does for
--quiet/--cron(accepting that a subcommand-levelflag is matched anywhere in the vector), or the banner moves after
parsing. Say which and why.
script/cibuildexits 0.Two smaller observations from the same review, fix if cheap:
Vaultik.Stderrnow has zero production writers andenv.stderrzeroreaders.
vaultik.godocuments keeping it deliberately — confirm thatstill reads true, or remove it.
bytesAttrKey's human-readable byte formatting does not survivegrouping (
x.bytesno longer matches the key check). Unreachabletoday; worth a comment if not a fix.
Implementation plan, on branch
fix-json-banneroffmain.1. The design question — extend
bannerSuppressedInArgs, do not move the banner.Moving the banner after parsing costs more than it buys.
root.goalready documents why it is printed first: it must appear even when
cobra rejects the arguments (
requires at least 2 arg(s)) and on--help. After parsing there is no single place that runs on all ofthose paths, so "after parsing" means reimplementing the banner in
several handlers, or accepting that it disappears from the failure
paths where a human most wants to know which build they just ran.
The stated objection to the scan is that
--jsonis a subcommand flagmatched anywhere in the vector. That objection applies equally to
--cron, which is already in the list and is also a subcommand flag —it exists only on
snapshot create(
internal/cli/snapshot.go:137). So this adds no new class ofimprecision, only another instance of one the code already accepts.
And the two failure directions are not symmetric: a false positive
loses a decorative banner, a false negative corrupts a document on
stdout. The scan should err toward suppression, and does.
2.
--jsonand--json=join--quiet/-q/--cronin the scan.--json=falsewill suppress the banner, exactly as--quiet=falsealready does. Making the value parse honest for
--jsonalone wouldmake it inconsistent with its two neighbours, and fixing all three is a
behaviour change nobody asked for; the doc comment states the bias
instead.
3. Tests, in
internal/cli. Two layers:TestEntryJSONStdoutIsExactlyOneDocumentrunsEntryitself with areal argument vector over the process's real stdout descriptor, all
the way through cobra and fx to the document, and asserts the capture
decodes as exactly one JSON value with nothing after it. It is
hermetic because
storage_url: file://is exempt from the S3credential checks and
snapshot listtreats a destination store withno
metadata/as an empty list rather than a failure — so theend-to-end path needs no network and no credentials.
--jsoncommands(plus
--jsonbefore the subcommand,--json=true, and aflag-with-value ahead of it), and a paired test asserting the banner
is still printed without a suppressing flag, so the first test cannot
be satisfied by deleting the banner.
4.
AGENTS.mdpolicy 9 re-worded to key on the log stream ratherthan stdout, preserving the operative rule.
5. The two smaller observations.
Vaultik.Stderr: kept, comment amended to say outright that nothingwrites to it today, so the next reader does not go looking for the
writer.
listEnv.stderrinsnapshot_list_test.gois genuinely dead— assigned, never read, because those tests capture the process's
stderr instead — and is removed.
bytesAttrKey: fixed rather than commented, since the fix is threelines. The key reaching
writeAttris group-qualified, so the matchis made against the final dot-separated segment. Covered by a test
over both the grouped and ungrouped renderings.
Verification:
script/cibuild, plusvaultik snapshot list --json | jq .against a real config with no other flags, and the same for
snapshot verify,snapshot remove,pruneandremote info. The new test willbe confirmed to fail against the unfixed
bannerSuppressedInArgsbyreverting it, not by assumption.
Implemented in
PR #109 (branch
fix-json-banner, one commit).Built.
--jsonand--json=join--quiet/-q/--croninbannerSuppressedInArgs. The design question is answered in favour ofextending the raw-argv scan: the banner is printed before parsing
deliberately, so that it survives cobra rejecting the arguments and
--help, and after parsing no single place covers those paths. Thesubcommand-vs-persistent distinction does not decide it, because
--cronis already in the list and is also a subcommand flag — itexists only on
snapshot create. Full reasoning in the PR body.AGENTS.mdpolicy 9 now names the log stream.bytesAttrKeymatches onthe final dot-separated segment, so byte formatting survives grouping.
Vaultik.Stderrkept with the comment amended to say outright thatnothing writes to it;
listEnv.stderrremoved as dead.Verified. The three new
internal/clitests were confirmed to failagainst the unfixed scan by reverting it, not by assumption — including
TestEntryJSONStdoutIsExactlyOneDocument, which runsEntryitself overthe process's real stdout descriptor through cobra and fx to the
document. End to end against a real config and a real snapshot, with no
other flags and no
-q:snapshot list --json | jq .,snapshot verify --json,snapshot remove --jsonandremote info --jsonall exit 0.script/cibuildexit 0, 15okpackages, 0
(cached), 134s.One item of the definition of done is not met, deliberately.
prune --json | jqstill fails, for a reason that is not the banner:pruneLocalSnapshotswrites three lines of prose to stdout with no--jsonawareness, it reproduces identically onmainbefore thischange, and
-qnever suppressed it either. Filed asissue #108 rather than
fixed here — a different writer on a different code path, the same
reason this issue was filed out of
issue #82 instead of
being folded into it. It also needs a decision this issue does not
contain: whether those lines belong in the document, on the logger, or
suppressed outright.