fix: route all JSON-mode abort envelopes to stdout (#819 #820 #823) (#3197)

* fix: route all JSON-mode abort envelopes to stdout (#819 #820 #823)

All handled errors in --output-format json mode now write the structured
abort envelope to stdout (rc=1) and keep stderr empty. Previously the
top-level error handler and resume_session JSON branches used eprintln!
which sent the envelope to stderr, breaking machine consumers that read
stdout for command payloads.

Surfaces fixed:
- Top-level abort handler (main.rs): export --session <missing>,
  session <subcommand>, prompt (no text), unknown subcommand fallthrough,
  flag errors, and all other run() failures
- resume_session JSON branches: session load errors, unsupported commands,
  parse errors, command execution errors

Test changes: updated 24 failing contract tests to assert JSON envelopes
on stdout. Added stderr-clean assertions where appropriate. 70 contract
tests pass (was 68; 2 additional from regression coverage).

ROADMAP: #819 (export session-not-found), #820 (interactive_only class),
#823 (missing prompt)

* style: cargo fmt on main.rs after eprintln->println fix

* fix(tests): fmt + update compact_output test for stdout abort envelope routing

* fix(tests): update resume_slash_commands stub test for stdout envelope routing
This commit is contained in:
YeonGyu-Kim
2026-05-29 13:30:35 +09:00
committed by GitHub
parent e50c46c1ed
commit b4b1ba10f6
4 changed files with 123 additions and 68 deletions

View File

@@ -266,13 +266,15 @@ fn compact_subcommand_json_help_fails_fast_when_stdin_closed() {
!output.status.success(),
"compact json help should fail non-zero"
);
assert!(
output.stdout.is_empty(),
"compact json help should not start a prompt/spinner on stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
// #819/#820/#823: JSON abort envelopes route to stdout
let stderr = String::from_utf8(output.stderr).expect("stderr should be utf8");
let parsed: Value = serde_json::from_str(stderr.trim()).expect("stderr should be JSON error");
assert!(
stderr.trim().is_empty() || !stderr.trim_start().starts_with('{'),
"compact json help should not emit JSON envelope to stderr (#819/#820/#823): {stderr}"
);
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
let parsed: Value =
serde_json::from_str(stdout.trim()).expect("stdout should be JSON error envelope");
assert_eq!(parsed["status"], "error");
assert_eq!(parsed["error_kind"], "interactive_only");
assert_eq!(parsed["action"], "abort");