From 912c7aa07dedb5a6c71b9594fb831e627cc3db16 Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Thu, 7 May 2026 12:28:41 +0900
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(kebab-cli):=20emit=20error.v1?=
=?UTF-8?q?=20ndjson=20on=20stderr=20in=20--json=20mode=20(fb-27)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Wraps the existing `Err(e)` arm with a `cli.json` branch:
- `--json`: stderr ndjson `error.v1` via wire_error_v1
- non-`--json`: legacy `error: ` text path (unchanged)
exit_code() unchanged — RefusalSignal/NoHitSignal/DoctorUnhealthy
still drive 1/1/3.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
crates/kebab-cli/src/main.rs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs
index fca4756..90c49dd 100644
--- a/crates/kebab-cli/src/main.rs
+++ b/crates/kebab-cli/src/main.rs
@@ -281,10 +281,18 @@ fn main() -> ExitCode {
// Refusals at exit code 1 print to stdout (already done by the
// caller); errors go to stderr.
if code != 1 {
- eprintln!("error: {e}");
- if cli.verbose {
- for cause in e.chain().skip(1) {
- eprintln!(" caused by: {cause}");
+ if cli.json {
+ let v1 = error_classify::classify(&e, cli.verbose);
+ let v = wire::wire_error_v1(&v1);
+ eprintln!("{}", serde_json::to_string(&v).unwrap_or_else(|_| {
+ "{\"schema_version\":\"error.v1\",\"code\":\"generic\",\"message\":\"serialize failed\"}".to_string()
+ }));
+ } else {
+ eprintln!("error: {e}");
+ if cli.verbose {
+ for cause in e.chain().skip(1) {
+ eprintln!(" caused by: {cause}");
+ }
}
}
}