mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-30 03:35:20 +08:00
fix(#776): resume command errors now return typed error_kind + non-null hint (invalid_history_count, session action errors)
This commit is contained in:
parent
028998d040
commit
2684737d9e
@ -7717,3 +7717,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
774. **`claw agents bogus`, `claw plugins bogus`, `claw mcp bogus` returned `hint: null`** — dogfooded 2026-05-27 on `727a1ea4`. Three "unknown subcommand" envelopes had `error_kind` correctly set but `hint: null`: (1) `unknown_agents_subcommand` — both text and JSON handler emitted single-line error with inline remediation after `.`, no `\n`; (2) `unknown_plugins_action` — same, period-delimited remediation; (3) `unknown_mcp_action` — `render_mcp_usage_json` never included a `hint` field at all. Fixes: (1)+(2) added `\n` before remediation suffix in `commands/src/lib.rs`; (3) added `hint` field to `render_mcp_usage_json` pointing at supported actions. All three now return non-null `hint`. 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori envelope-consistency probe on `727a1ea4`, 2026-05-27.
|
||||
|
||||
775. **Missing integration tests for #769-#771 interactive-only guards and #774 hint fields** — dogfooded 2026-05-27 on `c760a49c`. Fixes #769-#771 (session/cost/clear/memory/ultraplan/model/usage/stats/fork interactive-only guards) and #774 (agents/plugins/mcp unknown-subcommand hints) had no integration tests — a regression in any of those 10+ match arms would go undetected. Also: classify_error_kind unit test for `unknown_agents_subcommand` used the old single-line format string, not the `\n`-delimited format emitted after #774. Fixed: (1) updated unit test string to match new `\n`-delimited emission; (2) added `agents_plugins_mcp_unknown_subcommand_have_hint_774` asserting `error_kind` + non-null `hint` for all three; (3) added `interactive_only_guard_batch_769_to_771` asserting `interactive_only` + non-null `hint` for 10 cases. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori test-coverage sweep on `c760a49c`, 2026-05-27.
|
||||
|
||||
776. **Resume-mode JSON errors had opaque `error_kind:"resume_command_error"` + `hint:null`** — dogfooded 2026-05-27 on `028998d0` (pinpoint identified by Gaebal-gajae). `run_resume_command` returned errors (e.g. from `parse_history_count`) with hardcoded `error_kind:"resume_command_error"` and the full error string in `error` with no hint extraction. Wrappers had to regex prose instead of switching on typed fields. Three co-located gaps fixed: (1) `resume_session` JSON error path now applies `classify_error_kind` + `split_error_hint` so errors get specific `error_kind` (e.g. `invalid_history_count`) and non-null `hint`; (2) `parse_history_count` errors now use `invalid_history_count:` prefix + `\n` usage hint; (3) `/session exists|delete|switch|fork` missing-arg and unsupported-action errors now use `\n`-delimited format with `unsupported_resumed_command:` prefix. Existing test updated to match new error message format. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `028998d0`, 2026-05-27.
|
||||
|
||||
@ -323,6 +323,8 @@ fn classify_error_kind(message: &str) -> &'static str {
|
||||
"unsupported_config_section"
|
||||
} else if message.contains("unknown_plugins_action") {
|
||||
"unknown_plugins_action"
|
||||
} else if message.starts_with("invalid_history_count:") || message.contains("invalid count") {
|
||||
"invalid_history_count"
|
||||
} else if message.starts_with("missing_prompt:") {
|
||||
"missing_prompt"
|
||||
} else if message.contains("has been removed.") {
|
||||
@ -3370,14 +3372,20 @@ fn resume_session(session_path: &Path, commands: &[String], output_format: CliOu
|
||||
}
|
||||
Err(error) => {
|
||||
if output_format == CliOutputFormat::Json {
|
||||
// #776: classify + split so wrappers get typed fields instead of
|
||||
// hardcoded "resume_command_error" + prose in the error field
|
||||
let full_error = error.to_string();
|
||||
let error_kind = classify_error_kind(&full_error);
|
||||
let (short_reason, hint) = split_error_hint(&full_error);
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"kind": "resume_command_error",
|
||||
"kind": error_kind,
|
||||
"action": "resume",
|
||||
"status": "error",
|
||||
"error_kind": "resume_command_error",
|
||||
"error": error.to_string(),
|
||||
"error_kind": error_kind,
|
||||
"error": short_reason,
|
||||
"hint": hint,
|
||||
"exit_code": 2,
|
||||
"command": raw_command,
|
||||
})
|
||||
@ -6847,7 +6855,7 @@ fn run_resumed_session_command(
|
||||
}
|
||||
Some("exists") => {
|
||||
let Some(target) = target else {
|
||||
return Err("/session exists requires a session id".into());
|
||||
return Err("/session exists requires a session id.\nUsage: claw --resume <session> /session exists <session-id>".into());
|
||||
};
|
||||
let value = session_exists_json(target, &session.session_id)?;
|
||||
let exists = value
|
||||
@ -6866,7 +6874,7 @@ fn run_resumed_session_command(
|
||||
}
|
||||
Some("delete") => {
|
||||
let Some(target) = target else {
|
||||
return Err("/session delete requires a session id".into());
|
||||
return Err("/session delete requires a session id.\nUsage: claw --resume <session> /session delete <session-id> --force".into());
|
||||
};
|
||||
Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
@ -6883,7 +6891,7 @@ fn run_resumed_session_command(
|
||||
}
|
||||
Some("delete-force") => {
|
||||
let Some(target) = target else {
|
||||
return Err("/session delete requires a session id".into());
|
||||
return Err("/session delete requires a session id.\nUsage: claw --resume <session> /session delete <session-id> --force".into());
|
||||
};
|
||||
let handle = resolve_session_reference(target)?;
|
||||
if handle.id == session.session_id || handle.path == session_path {
|
||||
@ -6911,8 +6919,8 @@ fn run_resumed_session_command(
|
||||
})),
|
||||
})
|
||||
}
|
||||
Some("switch" | "fork") => Err("unsupported resumed slash command".into()),
|
||||
Some(other) => Err(format!("unsupported resumed /session action: {other}").into()),
|
||||
Some("switch" | "fork") => Err("unsupported_resumed_command: /session switch and /session fork require an interactive REPL.\nUsage: claw (then /session switch <id>) or claw --resume <session>".into()),
|
||||
Some(other) => Err(format!("unsupported_resumed_command: /session {other} is not supported in resume mode.\nSupported: list, exists, delete").into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -8413,11 +8421,12 @@ fn parse_history_count(raw: Option<&str>) -> Result<usize, String> {
|
||||
let Some(raw) = raw else {
|
||||
return Ok(DEFAULT_HISTORY_LIMIT);
|
||||
};
|
||||
// #776: use \n-delimited format so split_error_hint extracts hint into JSON envelopes
|
||||
let parsed: usize = raw
|
||||
.parse()
|
||||
.map_err(|_| format!("history: invalid count '{raw}'. Expected a positive integer."))?;
|
||||
.map_err(|_| format!("invalid_history_count: '{raw}' is not a positive integer.\nUsage: /history [count] (default: {DEFAULT_HISTORY_LIMIT})"))?;
|
||||
if parsed == 0 {
|
||||
return Err("history: count must be greater than 0.".to_string());
|
||||
return Err(format!("invalid_history_count: count must be greater than 0.\nUsage: /history [count] (default: {DEFAULT_HISTORY_LIMIT})"));
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
@ -15148,8 +15157,9 @@ UU conflicted.rs",
|
||||
let parsed = parse_history_count(raw);
|
||||
|
||||
// then
|
||||
assert!(parsed.is_err());
|
||||
assert!(parsed.unwrap_err().contains("invalid count 'abc'"));
|
||||
// #776: updated to match new invalid_history_count: prefix format
|
||||
let err = parsed.expect_err("non-numeric count should fail");
|
||||
assert!(err.contains("invalid_history_count:") && err.contains("'abc'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user