Skip to content

fix: Write error JSON envelope to stdout, not stderr#45

Open
rts1-godaddy wants to merge 2 commits into
mainfrom
fix/DEVEX-697
Open

fix: Write error JSON envelope to stdout, not stderr#45
rts1-godaddy wants to merge 2 commits into
mainfrom
fix/DEVEX-697

Conversation

@rts1-godaddy

Copy link
Copy Markdown
Collaborator

Summary

  • The rendered JSON envelope (success or error) now always goes to stdout. Previously, error envelopes were written to stderr, leaving stdout empty on failures — breaking agents and scripts that parse stdout for structured output.
  • The process exit code continues to signal success (0) vs failure (non-zero) to the shell.
    stderr remains reserved for diagnostics only (tracing, update notices, fatal framework errors).

Test plan

  • cargo fmt --all --check
  • cargo clippy --all-targets -- -D warnings
  • RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
  • cargo test --all-targets
  • cargo test --doc

Manual verification

Requires the godaddy/cli repo to build a binary against this branch.

Setup (one-time):

# In the cli repo, temporarily point at your local cli-engine:
# Edit rust/Cargo.toml:
  cli-engine = { features = ["pkce-auth"], path = "../../cli-engine" }

Test WITHOUT the fix (baseline):

cd cli-engine && git checkout main
cd ../cli/rust && cargo build --release && cp target/release/gddy ~/.local/bin/gddy

# Error output goes to stderr — stdout is empty:

gddy env set nonexistent-env --output json 2>/dev/null

# Expected: (empty)

Test WITH the fix:

cd cli-engine && git checkout fix/DEVEX-697
cd ../cli/rust && cargo build --release && cp target/release/gddy ~/.local/bin/gddy

# Error envelope now appears on stdout:
gddy env set nonexistent-env --output json 2>/dev/null
# Expected: {"error":{"code":"ERROR","message":"unknown environment \"nonexistent-env\"; known: ote, prod","system":"env"}}

# Confirm stderr is empty:
gddy env set nonexistent-env --output json 1>/dev/null
# Expected: (empty)

# Confirm exit code is still non-zero:
gddy env set nonexistent-env --output json > /dev/null 2>&1; echo $?
# Expected: 1

Cleanup: revert rust/Cargo.toml back to the published version before committing.

rts1-godaddy and others added 2 commits July 9, 2026 17:48
The CLI contract states stdout is always the JSON envelope (success or
error); stderr is reserved for diagnostics. Agents parsing stdout saw
empty output on failures because the error envelope was written to stderr.

Always write the rendered output to stdout regardless of exit code. The
exit code still communicates success/failure to the shell, and the
envelope's error field communicates it to JSON consumers.

Fixes: DEVEX-697
Co-authored-by: Cursor <cursoragent@cursor.com>
Standardizes PR descriptions with a Summary, Test plan, and Manual
verification section that guides reviewers through testing with and
without the change using a local cli-engine path override.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jpage-godaddy

Copy link
Copy Markdown
Collaborator

I really like that you are adding a pull request template, thanks for that.

Although this gap was identified, I'm not sure we actually want this. IMO the more common pattern is for errors to go to stderr, successful output to stdout.

@jpage-godaddy

Copy link
Copy Markdown
Collaborator

@copilot what are your thoughts on this? Is this change a best practice for a CLI?

Copilot AI commented Jul 10, 2026

Copy link
Copy Markdown

@copilot what are your thoughts on this? Is this change a best practice for a CLI?

Good question — there's a genuine tension here, and both conventions have merit.

The traditional UNIX argument (errors → stderr):
Most UNIX tools follow the rule that stderr carries diagnostics and errors, and stdout carries data. This lets shell pipelines like cli cmd 2>/dev/null | jq . work naturally, and it's what users expect from tools like git, curl, and the AWS CLI.

The structured-output argument (errors → stdout):
When a CLI operates in --output json mode it's making an explicit machine-readability contract. In that context, a consumer (agent, CI script, automation layer) reads stdout for the envelope. If the error envelope lands on stderr, stdout is empty on failure — the consumer either gets a parse error or silently drops the failure. The exit code tells the shell something failed, but it doesn't carry the structured error payload that the JSON consumer needs. This is closer to how HTTP APIs work: errors come back in the response body, not out-of-band.

My take:
For a CLI that emphasizes a --output json contract and is explicitly designed to be consumed by agents and automation (which this one is, per the README and AGENTS.md), writing the JSON envelope to stdout unconditionally is defensible and arguably more correct than following the UNIX stderr convention. The exit code still unambiguously signals success vs. failure to the shell.

That said, I recognize this is a design-level call that sits with the team. If the preference is to stay consistent with the broader UNIX/AWS CLI convention — errors to stderr, success to stdout — I'm happy to revert the change. Just let me know.

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