Write a JSON error object to stdout when a command fails under --output json - #71
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
There are a few correctness gaps in the new logic/tests (inference API detail not mapped into api_details, -q<expr> shorthand not detected for pre-parse JSON classification, and a test helper’s single-document assertion is unreliable) that should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the CLI’s JSON/JSONL output contract so that failures also emit a structured cmd.JSONErrorEnvelope to stdout (as the final JSON document), while continuing to print the plain-text error message (and usage, when applicable) to stderr. It also introduces a dedicated ErrInterrupted type and updates help/docs and tests to reflect the new behavior.
Changes:
- Add public
cmd.JSONErrorEnvelope/cmd.JSONErrortypes and document them in--help-output. - Emit JSON error envelopes on command failures under
--output json/jsonl, including for cobra pre-parse failures and interrupts; allow commands to suppress the envelope when their payload already reports failure. - Add/adjust tests for envelope emission behavior across API/local/usage/subcommand/interrupt cases and streamed jsonl commands.
File summaries
| File | Description |
|---|---|
| internal/help/output.go | Documents JSON error envelope behavior and renders schema codeblocks for help output. |
| internal/cmd/errors.go | Extracts api_* fields from HTTP client errors to populate the JSON error envelope. |
| internal/cmd/errors_test.go | New tests validating error envelope shape and stdout/stderr behavior across failure modes. |
| internal/cmd/command.model_push.go | Suppresses the envelope when model push payload already encodes the failure. |
| internal/cmd/command.model_push_test.go | Updates tests to reflect suppressed/enveloped JSON stdout behavior. |
| internal/cmd/command.model_deployment_logs_test.go | Updates streamed jsonl failure test to account for appended error envelope. |
| internal/cmd/command.go | Emits JSON envelopes for leaf failures; classifies cobra pre-parse failures as usage and envelopes them; moves usage output to stderr. |
| internal/cmd/command_context.go | Adds SuppressJSONError and centralized envelope emission (writeJSONError). |
| CONTRIBUTING.md | Documents the new JSON error envelope behavior and suppression hook. |
| cmd/errors.go | Adds ErrInterrupted, ErrorTypeName, and public JSON error envelope types. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
🚀 What
--output json/jsonlnow write a structured error to stdout, not just a message to stderr (fixes Provide error JSON object with message for JSON output on errors #68){"error": {"message", "type", "exit_code", "api_status_code"?, "api_error_code"?, "api_details"?}}, theapi_*fields only for failed API callsErrInterruptedtype, so exit code 130 is now listed in--help-outputExitUsage) instead of 1, and get an envelope too💻 How
cmd.JSONErrorEnvelope/JSONErrorin the public package, rendered as a schema inbaseten --help-outputapiErrorFieldspulls status, code, and details off the management/inference client errors already in the chain, best-effort: unparseable bodies contribute only the statusmessageis the error chain verbatim, so an API failure still carries the raw response bodyctx.SuppressJSONError()for commands whose payload already reports the failure (model push --wait), keeping stdout one document under--output json. Subprocess passthrough (truss) writes no envelope eitherExecuteclassifies them and reads--outputstraight from argvc.UsageString()to stderr; cobra'sUsage()writes to theSetOutwriter, which is stdout here🔬 Testing
internal/cmd/errors_test.go: management error with code and details, proxy 401 with neither, non-JSON 502, local failure, text mode, jsonl single line,--jqbypass, unknown flag, unknown subcommand, bad--outputvalue, interrupt, help docs