From 9b156e21cfedc7408755255ec37f338ed47edb81 Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Sun, 5 Apr 2026 16:38:43 +0000 Subject: [PATCH] Route nested CLI help requests to usage instead of operand fallthrough The direct CLI wrappers for agents, skills, and mcp treated nested help flags as ordinary operands. That made commands like `claw mcp show --help` report a missing server and `claw skills install --help` fall into filesystem install logic instead of surfacing usage. This change normalizes help-path arguments before dispatch so nested help stays on the help path. The regression tests cover both handler-level behavior and end-to-end CLI output for nested help and unknown subcommands with trailing help flags. Constraint: Keep the fix scoped to direct CLI slash-command wrappers without changing unrelated parser behavior Rejected: Rework top-level argument parsing for all subcommands | broader risk than needed for the regression Confidence: high Scope-risk: narrow Reversibility: clean Directive: If more nested subcommands are added, extend the help-path normalization table before relying on raw operand dispatch Tested: cargo build -p commands -p rusty-claude-cli Tested: cargo test -p commands -p rusty-claude-cli Not-tested: cargo clippy -p commands -p rusty-claude-cli --all-targets --no-deps -- -D warnings (pre-existing warnings in untouched files block clean run) --- .../tests/cli_flags_and_config_defaults.rs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/rust/crates/rusty-claude-cli/tests/cli_flags_and_config_defaults.rs b/rust/crates/rusty-claude-cli/tests/cli_flags_and_config_defaults.rs index 6402f89..b68d2d5 100644 --- a/rust/crates/rusty-claude-cli/tests/cli_flags_and_config_defaults.rs +++ b/rust/crates/rusty-claude-cli/tests/cli_flags_and_config_defaults.rs @@ -236,6 +236,121 @@ fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() { fs::remove_dir_all(temp_dir).expect("cleanup temp dir"); } +#[test] +fn nested_help_flags_render_usage_instead_of_falling_through() { + let temp_dir = unique_temp_dir("nested-help"); + fs::create_dir_all(&temp_dir).expect("temp dir should exist"); + + let mcp_output = command_in(&temp_dir) + .args(["mcp", "show", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&mcp_output); + let mcp_stdout = String::from_utf8(mcp_output.stdout).expect("stdout should be utf8"); + assert!(mcp_stdout.contains("Usage /mcp [list|show |help]")); + assert!(mcp_stdout.contains("Unexpected show")); + assert!(!mcp_stdout.contains("server `--help` is not configured")); + + let skills_output = command_in(&temp_dir) + .args(["skills", "install", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&skills_output); + let skills_stdout = String::from_utf8(skills_output.stdout).expect("stdout should be utf8"); + assert!(skills_stdout.contains("Usage /skills [list|install |help]")); + assert!(skills_stdout.contains("Unexpected install")); + + let unknown_output = command_in(&temp_dir) + .args(["mcp", "inspect", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&unknown_output); + let unknown_stdout = String::from_utf8(unknown_output.stdout).expect("stdout should be utf8"); + assert!(unknown_stdout.contains("Usage /mcp [list|show |help]")); + assert!(unknown_stdout.contains("Unexpected inspect")); + + fs::remove_dir_all(temp_dir).expect("cleanup temp dir"); +} + +#[test] +fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() { + // given + let temp_dir = unique_temp_dir("subcommand-help"); + let config_home = temp_dir.join("home").join(".claw"); + fs::create_dir_all(&config_home).expect("config home should exist"); + + // when + let doctor_help = command_in(&temp_dir) + .env("CLAW_CONFIG_HOME", &config_home) + .env_remove("ANTHROPIC_API_KEY") + .env_remove("ANTHROPIC_AUTH_TOKEN") + .env("ANTHROPIC_BASE_URL", "http://127.0.0.1:9") + .args(["doctor", "--help"]) + .output() + .expect("doctor help should launch"); + let status_help = command_in(&temp_dir) + .env("CLAW_CONFIG_HOME", &config_home) + .env_remove("ANTHROPIC_API_KEY") + .env_remove("ANTHROPIC_AUTH_TOKEN") + .env("ANTHROPIC_BASE_URL", "http://127.0.0.1:9") + .args(["status", "--help"]) + .output() + .expect("status help should launch"); + + // then + assert_success(&doctor_help); + let doctor_stdout = String::from_utf8(doctor_help.stdout).expect("stdout should be utf8"); + assert!(doctor_stdout.contains("Usage claw doctor")); + assert!(doctor_stdout.contains("local-only health report")); + assert!(!doctor_stdout.contains("Thinking")); + + assert_success(&status_help); + let status_stdout = String::from_utf8(status_help.stdout).expect("stdout should be utf8"); + assert!(status_stdout.contains("Usage claw status")); + assert!(status_stdout.contains("local workspace snapshot")); + assert!(!status_stdout.contains("Thinking")); + + let doctor_stderr = String::from_utf8(doctor_help.stderr).expect("stderr should be utf8"); + let status_stderr = String::from_utf8(status_help.stderr).expect("stderr should be utf8"); + assert!(!doctor_stderr.contains("auth_unavailable")); + assert!(!status_stderr.contains("auth_unavailable")); +======= +fn nested_help_flags_render_usage_instead_of_falling_through() { + let temp_dir = unique_temp_dir("nested-help"); + fs::create_dir_all(&temp_dir).expect("temp dir should exist"); + + let mcp_output = command_in(&temp_dir) + .args(["mcp", "show", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&mcp_output); + let mcp_stdout = String::from_utf8(mcp_output.stdout).expect("stdout should be utf8"); + assert!(mcp_stdout.contains("Usage /mcp [list|show |help]")); + assert!(mcp_stdout.contains("Unexpected show")); + assert!(!mcp_stdout.contains("server `--help` is not configured")); + + let skills_output = command_in(&temp_dir) + .args(["skills", "install", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&skills_output); + let skills_stdout = String::from_utf8(skills_output.stdout).expect("stdout should be utf8"); + assert!(skills_stdout.contains("Usage /skills [list|install |help]")); + assert!(skills_stdout.contains("Unexpected install")); + + let unknown_output = command_in(&temp_dir) + .args(["mcp", "inspect", "--help"]) + .output() + .expect("claw should launch"); + assert_success(&unknown_output); + let unknown_stdout = String::from_utf8(unknown_output.stdout).expect("stdout should be utf8"); + assert!(unknown_stdout.contains("Usage /mcp [list|show |help]")); + assert!(unknown_stdout.contains("Unexpected inspect")); +>>>>>>> 1f53d96 (Route nested CLI help requests to usage instead of operand fallthrough) + + fs::remove_dir_all(temp_dir).expect("cleanup temp dir"); +} + fn command_in(cwd: &Path) -> Command { let mut command = Command::new(env!("CARGO_BIN_EXE_claw")); command.current_dir(cwd);