fix(#768): --resume non-slash trailing arg now has error_kind:invalid_resume_argument + hint

This commit is contained in:
YeonGyu-Kim
2026-05-27 01:35:46 +09:00
parent 89735dbd33
commit b778d4e3d4
3 changed files with 56 additions and 1 deletions

View File

@@ -331,6 +331,9 @@ fn classify_error_kind(message: &str) -> &'static str {
} else if message.starts_with("unexpected extra arguments") {
// #766: extra positionals after commands that take no arguments (e.g. claw diff)
"unexpected_extra_args"
} else if message.starts_with("invalid_resume_argument:") {
// #768: --resume trailing arg is not a slash command
"invalid_resume_argument"
} else if message.starts_with("unknown_option:") {
"unknown_option"
} else if message.contains("is a slash command")
@@ -2102,7 +2105,10 @@ fn parse_resume_args(args: &[String], output_format: CliOutputFormat) -> Result<
}
if current_command.is_empty() {
return Err("--resume trailing arguments must be slash commands".to_string());
// #768: typed prefix + \n hint so split_error_hint() extracts hint into JSON envelope
return Err(format!(
"invalid_resume_argument: `{token}` is not a slash command.\nUsage: claw --resume <session-id|latest> /<slash-command> (e.g. /compact, /status)"
));
}
current_command.push(' ');
@@ -12957,6 +12963,13 @@ mod tests {
),
"removed_subcommand"
);
// #768: invalid resume trailing arg must classify as invalid_resume_argument
assert_eq!(
classify_error_kind(
"invalid_resume_argument: `compact` is not a slash command.\nUsage: claw --resume <session-id|latest> /<slash-command>"
),
"invalid_resume_argument"
);
}
#[test]

View File

@@ -1940,3 +1940,43 @@ fn diff_extra_args_have_typed_error_kind_and_hint_766() {
"claw diff --bogus must return non-null hint (#766), got: {hint:?}"
);
}
#[test]
fn resume_non_slash_trailing_arg_has_typed_error_kind_and_hint_768() {
// #768: `claw --resume latest compact` (missing leading /) returned
// error_kind:"unknown" + hint:null. Resume is orchestration-critical;
// wrappers need a machine-readable signal with a recovery hint.
let root = unique_temp_dir("resume-invalid-arg-768");
fs::create_dir_all(&root).expect("temp dir should exist");
let output = run_claw(
&root,
&["--output-format", "json", "--resume", "latest", "compact"],
&[],
);
assert!(
!output.status.success(),
"claw --resume latest compact should exit non-zero"
);
let stderr = String::from_utf8_lossy(&output.stderr);
let json_line = stderr
.lines()
.find(|l| l.trim_start().starts_with('{'))
.expect("stderr should contain a JSON error envelope");
let parsed: serde_json::Value =
serde_json::from_str(json_line).expect("error envelope should be valid JSON");
assert_eq!(
parsed["error_kind"], "invalid_resume_argument",
"non-slash resume trailing arg must return error_kind:invalid_resume_argument (#768)"
);
let hint = parsed["hint"].as_str().unwrap_or("");
assert!(
!hint.is_empty(),
"non-slash resume trailing arg must return non-null hint (#768), got: {hint:?}"
);
assert!(
hint.contains("/compact") || hint.contains("slash-command"),
"hint must reference slash-command usage, got: {hint:?}"
);
}