From 4fc63e338d3d868fff90f010867c2405203fae50 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 06:38:44 +0900 Subject: [PATCH 1/4] fix: support /plugins slash command in resume mode Move SlashCommand::Plugins out of the 'unsupported resumed slash command' catch-all and add a handler arm in run_resume_command that calls handle_plugins_slash_command for list/help actions. Mutation actions (install/uninstall/enable/disable) are rejected with a clear error since there is no runtime to reload in resume mode. Add /plugins coverage to resumed_inventory_commands test in output_format_contract.rs: kind, action, reload_runtime, target. Before: claw --resume session.jsonl /plugins --output-format json -> {error: 'unsupported resumed slash command', type: 'error'}, exit 1 After: claw --resume session.jsonl /plugins --output-format json -> {kind: 'plugin', action: 'list', ...}, exit 0 --- rust/crates/rusty-claude-cli/src/main.rs | 30 ++++++++++++++++++- .../tests/output_format_contract.rs | 29 ++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index ecbf3edd40..a331063edb 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -3545,6 +3545,35 @@ fn run_resume_command( json: Some(handle_skills_slash_command_json(args.as_deref(), &cwd)?), }) } + SlashCommand::Plugins { action, target } => { + // Only list/help are supported in resume mode (no runtime to reload) + match action.as_deref() { + Some("install") | Some("uninstall") | Some("enable") | Some("disable") => { + return Err( + "resumed /plugins mutations are interactive-only; start `claw` and run `/plugins` in the REPL".into(), + ); + } + _ => {} + } + let cwd = env::current_dir()?; + let loader = ConfigLoader::default_for(&cwd); + let runtime_config = loader.load()?; + let mut manager = build_plugin_manager(&cwd, &loader, &runtime_config); + let result = handle_plugins_slash_command(action.as_deref(), target.as_deref(), &mut manager)?; + let action_str = action.as_deref().unwrap_or("list"); + let json = serde_json::json!({ + "kind": "plugin", + "action": action_str, + "target": target, + "message": &result.message, + "reload_runtime": result.reload_runtime, + }); + Ok(ResumeCommandOutcome { + session: session.clone(), + message: Some(result.message), + json: Some(json), + }) + } SlashCommand::Doctor => { let report = render_doctor_report()?; Ok(ResumeCommandOutcome { @@ -3631,7 +3660,6 @@ fn run_resume_command( | SlashCommand::Model { .. } | SlashCommand::Permissions { .. } | SlashCommand::Session { .. } - | SlashCommand::Plugins { .. } | SlashCommand::Login | SlashCommand::Logout | SlashCommand::Vim 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 3c7d1f904f..b6b808363c 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -397,6 +397,35 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() { agents["count"].is_number(), "count must be a number, not a text render" ); + + + let plugins = assert_json_command_with_env( + &root, + &[ + "--output-format", + "json", + "--resume", + session_path.to_str().expect("utf8 session path"), + "/plugins", + ], + &[ + ( + "CLAW_CONFIG_HOME", + config_home.to_str().expect("utf8 config home"), + ), + ("HOME", home.to_str().expect("utf8 home")), + ], + ); + assert_eq!(plugins["kind"], "plugin"); + assert_eq!(plugins["action"], "list"); + assert!( + plugins["reload_runtime"].is_boolean(), + "plugins reload_runtime should be a boolean" + ); + assert!( + plugins["target"].is_null(), + "plugins target should be null when no plugin is targeted" + ); } #[test] From 08fc38f51bf38a331681ee9e44b4c7b3b41e564a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 06:42:53 +0900 Subject: [PATCH 2/4] style: cargo fmt line wrap in run_resume_command plugins handler --- rust/crates/rusty-claude-cli/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index a331063edb..9bbd7fa41a 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -3559,7 +3559,8 @@ fn run_resume_command( let loader = ConfigLoader::default_for(&cwd); let runtime_config = loader.load()?; let mut manager = build_plugin_manager(&cwd, &loader, &runtime_config); - let result = handle_plugins_slash_command(action.as_deref(), target.as_deref(), &mut manager)?; + let result = + handle_plugins_slash_command(action.as_deref(), target.as_deref(), &mut manager)?; let action_str = action.as_deref().unwrap_or("list"); let json = serde_json::json!({ "kind": "plugin", From c956e467514c21ba9a065fb507fd73020b7b8445 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 1 May 2026 06:49:50 +0900 Subject: [PATCH 3/4] fix: block /plugins update in resume mode, fix comment Address REQUEST_CHANGES from OMX review: 1. Add 'update' to the blocked mutation actions in resume mode (previously only install/uninstall/enable/disable were blocked) 2. Fix comment: 'Only list is supported' instead of 'Only list/help' since /plugins help doesn't actually parse as a valid action --- rust/crates/rusty-claude-cli/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 9bbd7fa41a..11539c9a01 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -3546,9 +3546,10 @@ fn run_resume_command( }) } SlashCommand::Plugins { action, target } => { - // Only list/help are supported in resume mode (no runtime to reload) + // Only list is supported in resume mode (no runtime to reload) match action.as_deref() { - Some("install") | Some("uninstall") | Some("enable") | Some("disable") => { + Some("install") | Some("uninstall") | Some("enable") | Some("disable") + | Some("update") => { return Err( "resumed /plugins mutations are interactive-only; start `claw` and run `/plugins` in the REPL".into(), ); From bac47d91bdeb705e12caca98b9b63d2bde08463f Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 5 May 2026 04:53:55 +0900 Subject: [PATCH 4/4] style: cargo fmt after conflict resolution --- rust/crates/rusty-claude-cli/tests/output_format_contract.rs | 1 - 1 file changed, 1 deletion(-) 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 b6b808363c..ab2944ebfb 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -398,7 +398,6 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() { "count must be a number, not a text render" ); - let plugins = assert_json_command_with_env( &root, &[