fix(#784): export --output missing-value and extra-positional errors now return typed error_kind + non-null hint

This commit is contained in:
YeonGyu-Kim
2026-05-27 08:07:32 +09:00
parent 81fe0ccbb7
commit e628b4bb68
3 changed files with 75 additions and 3 deletions

View File

@@ -349,8 +349,11 @@ fn classify_error_kind(message: &str) -> &'static str {
} else if message.contains("has been removed.") {
// #765: removed subcommands (login, logout) — hint contains migration guidance
"removed_subcommand"
} else if message.starts_with("unexpected extra arguments") {
} else if message.starts_with("unexpected extra arguments")
|| message.starts_with("unexpected_extra_args:")
{
// #766: extra positionals after commands that take no arguments (e.g. claw diff)
// #784: export extra-positional errors use the typed prefix form
"unexpected_extra_args"
} else if message.starts_with("invalid_resume_argument:") {
// #768: --resume trailing arg is not a slash command
@@ -2113,7 +2116,7 @@ fn parse_export_args(args: &[String], output_format: CliOutputFormat) -> Result<
"--output" | "-o" => {
let value = args
.get(index + 1)
.ok_or_else(|| format!("missing value for {}", args[index]))?;
.ok_or_else(|| format!("missing_flag_value: missing value for {}.\nUsage: claw export [PATH] [--session SESSION] [--output PATH]", args[index]))?;
output_path = Some(PathBuf::from(value));
index += 2;
}
@@ -2129,7 +2132,8 @@ fn parse_export_args(args: &[String], output_format: CliOutputFormat) -> Result<
index += 1;
}
other => {
return Err(format!("unexpected export argument: {other}"));
// #784: use typed prefix so classify_error_kind returns unexpected_extra_args
return Err(format!("unexpected_extra_args: unexpected export argument: {other}.\nUsage: claw export [PATH] [--session SESSION] [--output PATH]"));
}
}
}

View File

@@ -2436,3 +2436,69 @@ fn init_json_envelope_has_hint_and_already_initialized_783() {
"re-init hint should acknowledge workspace exists, got: {hint2:?}"
);
}
#[test]
fn export_arg_errors_have_typed_kind_and_hint_784() {
// #784: `claw export --output` (missing flag value) returned error_kind:"unknown" + hint:null.
// `claw export a.md b.md` (extra positional) also returned unknown+null.
// Both export arg errors now use typed prefixes + usage hint.
let root = unique_temp_dir("export-arg-errors-784");
fs::create_dir_all(&root).expect("temp dir");
std::process::Command::new("git")
.args(["init", "-q"])
.current_dir(&root)
.output()
.ok();
// Missing --output value
let out1 = run_claw(
&root,
&["--output-format", "json", "export", "--output"],
&[],
);
assert!(!out1.status.success(), "--output with no value should fail");
let stderr1 = String::from_utf8_lossy(&out1.stderr);
let j1: serde_json::Value = stderr1
.lines()
.find(|l| l.trim_start().starts_with('{'))
.and_then(|l| serde_json::from_str(l).ok())
.expect("missing --output should emit JSON error");
assert_eq!(
j1["error_kind"], "missing_flag_value",
"missing --output value should be missing_flag_value, got {:?}",
j1["error_kind"]
);
let h1 = j1["hint"]
.as_str()
.expect("missing_flag_value must have hint (#784)");
assert!(
!h1.is_empty() && h1.contains("export"),
"hint must reference export usage, got: {h1:?}"
);
// Extra positional argument
let out2 = run_claw(
&root,
&["--output-format", "json", "export", "first.md", "second.md"],
&[],
);
assert!(!out2.status.success(), "extra positional should fail");
let stderr2 = String::from_utf8_lossy(&out2.stderr);
let j2: serde_json::Value = stderr2
.lines()
.find(|l| l.trim_start().starts_with('{'))
.and_then(|l| serde_json::from_str(l).ok())
.expect("extra positional should emit JSON error");
assert_eq!(
j2["error_kind"], "unexpected_extra_args",
"extra positional should be unexpected_extra_args, got {:?}",
j2["error_kind"]
);
let h2 = j2["hint"]
.as_str()
.expect("unexpected_extra_args must have hint (#784)");
assert!(
!h2.is_empty() && h2.contains("export"),
"hint must reference export usage, got: {h2:?}"
);
}