Skip to content

fix(bundle): send command errors to stderr so --json stdout stays parseable#3235

Merged
mnriem merged 1 commit into
github:mainfrom
jawwad-ali:fix/bundle-json-error-stdout
Jun 29, 2026
Merged

fix(bundle): send command errors to stderr so --json stdout stays parseable#3235
mnriem merged 1 commit into
github:mainfrom
jawwad-ali:fix/bundle-json-error-stdout

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Description

The bundle command group documents its output contract as:

--json emits machine-readable data on stdout; human logs go to stderr/console.

and its _fail() helper's docstring says "Print an actionable error to stderr and exit non-zero." But _fail called console.print(...), and the shared console (_console.console = Console(highlight=False)) writes to stdout. Every bundle subcommand routes failures through _fail (search, info, list, install, update, remove, validate, build, init), so under --json an error printed Error: ... Rich text onto stdout, corrupting the JSON stream a consumer parses.

Fix

Add a stderr-bound err_console to _console.py (the module documented as the single source of Rich Console instances) and use it in _fail. stdout now carries only the JSON payload.

Testing

  • uvx ruff check clean; full tests/contract/test_bundle_cli.py passes (24 tests; the existing outside-project guidance test still passes).
  • New test_fail_writes_error_to_stderr_not_stdout: asserts _fail writes to stderr and not stdout. Fails before (message was on stdout), passes after.
  • Verified directly: _fail('boom') now writes Error: boom to stderr with stdout empty.

AI Disclosure

  • I did use AI assistance (describe below)

Found and fixed with Claude Code (Claude Opus 4.8) under my direction. AI traced _failconsole → stdout against the module's stated --json/stderr contract; I reproduced the stdout contamination, confirmed the existing suite still passes, and reviewed the diff before submitting.

…seable

The bundle command group's _fail() helper is documented as printing 'to stderr', and the module contract is 'human logs go to stderr/console' while --json 'emits machine-readable data on stdout'. But it called console.print(), and the shared console writes to STDOUT, so every bundle error (every command routes through _fail) landed on stdout -- corrupting the JSON stream that --json consumers parse.

Add a stderr-bound err_console to _console.py (its documented role as the single Console source) and use it in _fail. stdout now carries only the JSON payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the specify bundle CLI output contract so that error/diagnostic messages are written to stderr, keeping stdout clean and parseable for --json consumers.

Changes:

  • Introduces a dedicated Rich err_console bound to stderr in the shared console module.
  • Routes bundle command failures through err_console via the _fail() helper instead of the stdout-bound console.
  • Adds a contract test ensuring _fail() writes the error message to stderr and not stdout.
Show a summary per file
File Description
src/specify_cli/_console.py Adds a stderr-bound Rich console (err_console) to centralize error/diagnostic output.
src/specify_cli/commands/bundle/__init__.py Updates _fail() to print errors via err_console, preventing stdout JSON corruption under --json.
tests/contract/test_bundle_cli.py Adds a regression test asserting _fail() writes to stderr and not stdout.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Low

@mnriem mnriem merged commit c5fb3dc into github:main Jun 29, 2026
12 checks passed
@mnriem

mnriem commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants