Skip to content

runHandler()/runBuiltinUpdate() write errors to stdout, not stderr #133

Description

@mrbarboza

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

  1. Add an optional stderr?: { write: (chunk: string) => unknown } field to AxiCliOptions, defaulting to process.stderr (mirroring how stdout already defaults to process.stdout).
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions