Problem
runHandler() and runBuiltinUpdate() in packages/axi-sdk-js/src/cli.ts (built to dist/cli.js, both functions live around lines 89-119) write both success output and error output to the same stdout stream passed via AxiCliOptions.stdout. There is no separate stream for errors, so every consuming CLI's error paths land on stdout instead of stderr — breaking clig.dev's "send diagnostics to stderr so scripts can separate the two streams" guideline for every consumer of this SDK.
Repro (via a consumer, jira-axi, using axi-sdk-js 0.1.9/0.1.10)
$ node dist/bin/jira-axi.js bogus 1>/tmp/o 2>/tmp/e; echo EXIT:$?
EXIT:2
$ cat /tmp/o
error: "Unknown command: bogus"
code: VALIDATION_ERROR
help[1]: Run `--help` to see available commands
$ cat /tmp/e
(empty)
Reproduces identically for every error path we exercised: unauthenticated commands, blocked writes, unknown --site, leading-flag errors, missing-version errors — exit code is always set correctly via process.exitCode, but stderr is always empty and the error text lands on stdout instead.
Root cause
In dist/cli.js, both catch blocks do:
const formatted = (options.formatError ?? defaultFormatError)(error);
stdout.write(formatted.output);
process.exitCode = formatted.exitCode;
using the same stdout stream passed in via options.stdout for both success and error output. The two early-return AxiError-shaped writes (missing-version and leading-flag errors, around dist/cli.js:45 and dist/cli.js:62) have the same issue.
Suggested fix
- Add an optional
stderr?: { write: (chunk: string) => unknown } field to AxiCliOptions, defaulting to process.stderr (mirroring how stdout already defaults to process.stdout).
- In
runHandler() and runBuiltinUpdate(), keep success-path output (renderCommandOutput, renderOutput) on stdout, but write the error-path output (formatted.output, and the two early-return AxiError writes) to the new stderr stream instead.
exitCodeForError() / exit-code semantics are unaffected — this is a pure stream-routing change, no behavior change to what gets printed or the exit code.
Confirmed against the latest published version (0.1.10, 2026-08-07) that this is still unfixed there.
Happy to send a PR if useful — this is a contained, mechanical change with no new dependencies.
Problem
runHandler()andrunBuiltinUpdate()inpackages/axi-sdk-js/src/cli.ts(built todist/cli.js, both functions live around lines 89-119) write both success output and error output to the samestdoutstream passed viaAxiCliOptions.stdout. There is no separate stream for errors, so every consuming CLI's error paths land on stdout instead of stderr — breaking clig.dev's "send diagnostics to stderr so scripts can separate the two streams" guideline for every consumer of this SDK.Repro (via a consumer, jira-axi, using axi-sdk-js 0.1.9/0.1.10)
Reproduces identically for every error path we exercised: unauthenticated commands, blocked writes, unknown
--site, leading-flag errors, missing-version errors — exit code is always set correctly viaprocess.exitCode, but stderr is always empty and the error text lands on stdout instead.Root cause
In
dist/cli.js, both catch blocks do:using the same
stdoutstream passed in viaoptions.stdoutfor both success and error output. The two early-returnAxiError-shaped writes (missing-version and leading-flag errors, arounddist/cli.js:45anddist/cli.js:62) have the same issue.Suggested fix
stderr?: { write: (chunk: string) => unknown }field toAxiCliOptions, defaulting toprocess.stderr(mirroring howstdoutalready defaults toprocess.stdout).runHandler()andrunBuiltinUpdate(), keep success-path output (renderCommandOutput,renderOutput) onstdout, but write the error-path output (formatted.output, and the two early-returnAxiErrorwrites) to the newstderrstream instead.exitCodeForError()/ exit-code semantics are unaffected — this is a pure stream-routing change, no behavior change to what gets printed or the exit code.Confirmed against the latest published version (0.1.10, 2026-08-07) that this is still unfixed there.
Happy to send a PR if useful — this is a contained, mechanical change with no new dependencies.