0194 — Printing is the exclusion, not being native
Status: Accepted Date: 2026-09-22
Context
0192 put native commands
outside createCliInvoker and gave the reason in its Consequences: “they write
to stdout and stderr by construction — that is what they are for — and an
operation that prints has no result to return.”
That reason describes one half of CliCommandDefinition, and the type has
always had two. CliCommandDefinitionWithOutput declares output, returns a
value the frame validates and prints, and holds the writers without normally
touching them. CliCommandDefinitionWithoutOutput declares output?: never,
returns void, and prints itself. The exclusion was written for the second and
applied to both.
A consumer measured the cost on the upgrade to 0.92.0: six of their eight native
commands declare output, a describe line that had worked in their stream —
by re-spawning the binary, which is exactly what 0192 removed — began answering
NOT_FOUND. They accepted the narrowing and reported it. It was not a trade the
decision ever meant to make; it was a criterion that named the wrong property.
Decision
The invoker admits a native command if and only if it declares output. The
test is cliCommandReturnsResult, a type predicate, so the compiler carries the
distinction the prose got wrong.
present is not called on this path. It is stdout formatting and there is no
stdout; calling it to throw the result away would run consumer code for no
effect and let a throw in it turn a good result into an error.
exitCode is applied, through the same cliCommandSuccessExitCode the command
line uses. A script that branches on the code must not be able to see the two
paths disagree, and one shared function is the only way that holds.
Whatever a handler does write comes back on the result as written. In process
there is nowhere else for it to go: printing it would interleave with the
caller’s own output and corrupt a stream of JSON lines, and dropping it would
lose a diagnostic without saying so. The caller decides what it is worth.
Consequences
A printing command is still outside, and now for its own reason rather than by
association. NOT_FOUND is the honest answer for it: it has no result to give,
and running it would write into a caller that asked for a value.
The consequence 0192 drew — “a stream cannot invoke the stream command, so the consumer’s ban on nesting has nothing left to forbid” — survives unchanged, because a stream command is precisely one that prints.
This widens a released surface: a name that answered NOT_FOUND in 0.92.0 can
now resolve. An application that relied on native commands being unreachable in
process has to say so by not passing them to the invoker.