From bae0099c7ccf7a167221a69d5882582c39c88f51 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 26 May 2026 02:33:26 +0900 Subject: [PATCH] fix(#711): add missing action fields to version/system-prompt/export/init json responses; add contract test assertions --- ROADMAP.md | 2 ++ rust/crates/rusty-claude-cli/src/main.rs | 6 ++++++ .../rusty-claude-cli/tests/output_format_contract.rs | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 4d816e12..25cad837 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7587,3 +7587,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 709. **`render_agents_report_json` and `render_skill_install_report_json` in `commands/src/lib.rs` contained duplicate `"status":"ok"` keys in the same JSON object literal — second key silently overwrites first in serde_json** — found during #708 dogfood sweep on `47c0226a`. Rust `serde_json::json!` macros accept duplicate keys but `serde_json` silently keeps the last occurrence; any consumer relying on the first occurrence gets the wrong value if they are ever different. Fixed by removing the duplicate `status` key from `render_agents_report_json` and `render_skill_install_report_json`. Source: Jobdori dogfood on `47c0226a`, 2026-05-26. 710. **`claw diff --output-format json` missing `action` and `working_directory` fields — both the ok and error paths in `render_diff_json_for` omitted `action` entirely (returning `null` at parse time) and omitted `working_directory`** — dogfooded 2026-05-26 on `8f8eb41e`. Both the clean/changes success path and the no-git-repo error path were missing the two envelope fields that automation uses for routing and provenance. Fix: added `action:"diff"` and `working_directory: cwd.display()` to both branches of `render_diff_json_for`; added contract-test assertions for both fields. Source: Jobdori dogfood on `8f8eb41e`, 2026-05-26. + +711. **`version`, `system-prompt`, `export`, and `init` `--output-format json` responses all lacked an `action` field — returned `null` or omitted entirely** — dogfooded 2026-05-26 on `42c17bc4`. All four commands emitted `kind` + `status` but no `action`, making them inconsistent with all other JSON surfaces that include the verb. Fix: added `action:"show"` to `version` and `system-prompt`, `action:"export"` to all three `export` paths, `action:"init"` to `init`; added contract-test assertions for each. Source: Jobdori dogfood on `42c17bc4`, 2026-05-26. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 4da97c0b..d9b5ec8a 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -2954,6 +2954,7 @@ fn print_system_prompt( "{}", serde_json::to_string_pretty(&json!({ "kind": "system-prompt", + "action": "show", "status": "ok", "message": message, "sections": sections, @@ -2977,6 +2978,7 @@ fn version_json_value() -> serde_json::Value { let executable_path = env::current_exe().ok().map(|p| p.display().to_string()); json!({ "kind": "version", + "action": "show", "status": "ok", "message": render_version_report(), "version": VERSION, @@ -4174,6 +4176,7 @@ fn run_resume_command( )), json: Some(serde_json::json!({ "kind": "export", + "action": "export", "status": "ok", "file": export_path.display().to_string(), "message_count": msg_count, @@ -7603,6 +7606,7 @@ fn init_json_value(report: &crate::init::InitReport, message: &str) -> serde_jso let status = "ok"; json!({ "kind": "init", + "action": "init", "status": status, "project_path": report.project_root.display().to_string(), "created": report.artifacts_with_status(InitStatus::Created), @@ -8209,6 +8213,7 @@ fn run_export( "{}", serde_json::to_string_pretty(&json!({ "kind": "export", + "action": "export", "status": "ok", "message": report, "session_id": handle.id, @@ -8231,6 +8236,7 @@ fn run_export( "{}", serde_json::to_string_pretty(&json!({ "kind": "export", + "action": "export", "status": "ok", "session_id": handle.id, "file": handle.path.display().to_string(), diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 51b21298..eddfde94 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -73,6 +73,10 @@ fn version_emits_json_when_requested() { let parsed = assert_json_command(&root, &["--output-format", "json", "version"]); assert_eq!(parsed["kind"], "version"); + assert_eq!( + parsed["action"], "show", + "version JSON must have action:show (#711)" + ); assert_eq!(parsed["version"], env!("CARGO_PKG_VERSION")); // Provenance fields must be present for binary identification (#507). assert!( @@ -478,6 +482,10 @@ fn bootstrap_and_system_prompt_emit_json_when_requested() { let prompt = assert_json_command(&root, &["--output-format", "json", "system-prompt"]); assert_eq!(prompt["kind"], "system-prompt"); + assert_eq!( + prompt["action"], "show", + "system-prompt JSON must have action:show (#711)" + ); assert!(prompt["message"] .as_str() .expect("prompt text") @@ -508,6 +516,10 @@ fn dump_manifests_and_init_emit_json_when_requested() { fs::create_dir_all(&workspace).expect("workspace should exist"); let init = assert_json_command(&workspace, &["--output-format", "json", "init"]); assert_eq!(init["kind"], "init"); + assert_eq!( + init["action"], "init", + "init JSON must have action:init (#711)" + ); assert!(workspace.join("CLAUDE.md").exists()); }