mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-28 16:36:45 +00:00
fix(#778): doctor check JSON objects now include hint field with stable remediation text for warn/fail checks
This commit is contained in:
@@ -7721,3 +7721,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
|||||||
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.
|
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.
|
||||||
|
|
||||||
777. **Resumed `/plugins install|enable|disable|uninstall|update` returned opaque error_kind instead of interactive_only** — dogfooded 2026-05-27 on `2684737d` (pinpoint by Gaebal-gajae). The mutation arm in `run_resume_command` returned a bare single-line error; after #776 it was classified/split by the caller but fell to `error_kind:"unknown"` + `hint:null` because there was no `interactive_only:` prefix. Orchestrators had no stable signal to distinguish "command rejected — switch to REPL" from a transient error. Fix: each mutation verb now returns `interactive_only: /plugins {action} requires a live session...\n...hint...` so the caller emits `error_kind:"interactive_only"` + non-null hint pointing at REPL or direct CLI. Integration test `resume_plugin_mutations_are_typed_interactive_only_777` covers all 5 mutation verbs. 39 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `2684737d`, 2026-05-27.
|
777. **Resumed `/plugins install|enable|disable|uninstall|update` returned opaque error_kind instead of interactive_only** — dogfooded 2026-05-27 on `2684737d` (pinpoint by Gaebal-gajae). The mutation arm in `run_resume_command` returned a bare single-line error; after #776 it was classified/split by the caller but fell to `error_kind:"unknown"` + `hint:null` because there was no `interactive_only:` prefix. Orchestrators had no stable signal to distinguish "command rejected — switch to REPL" from a transient error. Fix: each mutation verb now returns `interactive_only: /plugins {action} requires a live session...\n...hint...` so the caller emits `error_kind:"interactive_only"` + non-null hint pointing at REPL or direct CLI. Integration test `resume_plugin_mutations_are_typed_interactive_only_777` covers all 5 mutation verbs. 39 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `2684737d`, 2026-05-27.
|
||||||
|
|
||||||
|
778. **`claw doctor --output-format json` check objects had no `hint` field — all warn/fail remediation was buried in `details_prose`** — dogfooded 2026-05-27 on `e0203036`. Automation had to parse prose strings to find remediation text instead of reading a stable `hint` field. `DiagnosticCheck.json_value()` never emitted a `hint` field. Fix: added `hint: Option<String>` field to `DiagnosticCheck`, added `with_hint()` builder, populated for all warn/fail cases (auth: set env var; config: fix JSON syntax; workspace: git init; boot_preflight: install missing binaries; sandbox: expected on non-Linux). Empty hint string collapses to `null` (ok checks). 39 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori doctor-envelope probe on `e0203036`, 2026-05-27.
|
||||||
|
|||||||
@@ -2212,6 +2212,9 @@ struct DiagnosticCheck {
|
|||||||
summary: String,
|
summary: String,
|
||||||
details: Vec<String>,
|
details: Vec<String>,
|
||||||
data: Map<String, Value>,
|
data: Map<String, Value>,
|
||||||
|
/// #778: stable remediation hint for warn/fail checks so automation can read
|
||||||
|
/// a structured field instead of parsing details_prose.
|
||||||
|
hint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticCheck {
|
impl DiagnosticCheck {
|
||||||
@@ -2222,6 +2225,7 @@ impl DiagnosticCheck {
|
|||||||
summary: summary.into(),
|
summary: summary.into(),
|
||||||
details: Vec::new(),
|
details: Vec::new(),
|
||||||
data: Map::new(),
|
data: Map::new(),
|
||||||
|
hint: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2235,6 +2239,14 @@ impl DiagnosticCheck {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_hint(mut self, hint: impl Into<String>) -> Self {
|
||||||
|
let h = hint.into();
|
||||||
|
if !h.is_empty() {
|
||||||
|
self.hint = Some(h);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn json_value(&self) -> Value {
|
fn json_value(&self) -> Value {
|
||||||
// Derive a stable snake_case id from the check name for machine-readable keying (#704).
|
// Derive a stable snake_case id from the check name for machine-readable keying (#704).
|
||||||
let id = self
|
let id = self
|
||||||
@@ -2297,6 +2309,14 @@ impl DiagnosticCheck {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
// #778: include hint field so automation can read remediation without parsing prose
|
||||||
|
value.insert(
|
||||||
|
"hint".to_string(),
|
||||||
|
self.hint
|
||||||
|
.as_deref()
|
||||||
|
.map(|h| Value::String(h.to_string()))
|
||||||
|
.unwrap_or(Value::Null),
|
||||||
|
);
|
||||||
value.extend(self.data.clone());
|
value.extend(self.data.clone());
|
||||||
Value::Object(value)
|
Value::Object(value)
|
||||||
}
|
}
|
||||||
@@ -2596,6 +2616,7 @@ fn check_auth_health() -> DiagnosticCheck {
|
|||||||
"Suggested action set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN; `claw login` is removed"
|
"Suggested action set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN; `claw login` is removed"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
])
|
])
|
||||||
|
.with_hint("Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN env var. The saved OAuth token is no longer accepted.")
|
||||||
.with_data(Map::from_iter([
|
.with_data(Map::from_iter([
|
||||||
("api_key_present".to_string(), json!(api_key_present)),
|
("api_key_present".to_string(), json!(api_key_present)),
|
||||||
("auth_token_present".to_string(), json!(auth_token_present)),
|
("auth_token_present".to_string(), json!(auth_token_present)),
|
||||||
@@ -2624,6 +2645,7 @@ fn check_auth_health() -> DiagnosticCheck {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_details(vec![env_details])
|
.with_details(vec![env_details])
|
||||||
|
.with_hint(if !any_auth_present { "Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN to authenticate." } else { "" })
|
||||||
.with_data(Map::from_iter([
|
.with_data(Map::from_iter([
|
||||||
("api_key_present".to_string(), json!(api_key_present)),
|
("api_key_present".to_string(), json!(api_key_present)),
|
||||||
("auth_token_present".to_string(), json!(auth_token_present)),
|
("auth_token_present".to_string(), json!(auth_token_present)),
|
||||||
@@ -2637,6 +2659,7 @@ fn check_auth_health() -> DiagnosticCheck {
|
|||||||
DiagnosticLevel::Fail,
|
DiagnosticLevel::Fail,
|
||||||
format!("failed to inspect legacy saved credentials: {error}"),
|
format!("failed to inspect legacy saved credentials: {error}"),
|
||||||
)
|
)
|
||||||
|
.with_hint("Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN env var to authenticate.")
|
||||||
.with_data(Map::from_iter([
|
.with_data(Map::from_iter([
|
||||||
("api_key_present".to_string(), json!(api_key_present)),
|
("api_key_present".to_string(), json!(api_key_present)),
|
||||||
("auth_token_present".to_string(), json!(auth_token_present)),
|
("auth_token_present".to_string(), json!(auth_token_present)),
|
||||||
@@ -2728,6 +2751,7 @@ fn check_config_health(
|
|||||||
.map(|path| format!("Discovered file {path}"))
|
.map(|path| format!("Discovered file {path}"))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
.with_hint("Fix the JSON syntax error in the listed config file, then rerun `claw doctor`.")
|
||||||
.with_data(Map::from_iter([
|
.with_data(Map::from_iter([
|
||||||
("discovered_files".to_string(), json!(discovered_paths)),
|
("discovered_files".to_string(), json!(discovered_paths)),
|
||||||
(
|
(
|
||||||
@@ -2791,6 +2815,13 @@ fn check_workspace_health(context: &StatusContext) -> DiagnosticCheck {
|
|||||||
"current directory is not inside a git project".to_string()
|
"current directory is not inside a git project".to_string()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.with_hint(if !in_repo {
|
||||||
|
"Run `git init` to initialise a repository, or `cd` into a git project."
|
||||||
|
} else if stale_base_warning.is_some() {
|
||||||
|
"Rebase or merge to bring the branch up to date with its base."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
})
|
||||||
.with_details(vec![
|
.with_details(vec![
|
||||||
format!("Cwd {}", context.cwd.display()),
|
format!("Cwd {}", context.cwd.display()),
|
||||||
format!(
|
format!(
|
||||||
@@ -2930,6 +2961,16 @@ fn check_boot_preflight_health(context: &StatusContext) -> DiagnosticCheck {
|
|||||||
preflight.summary(),
|
preflight.summary(),
|
||||||
)
|
)
|
||||||
.with_details(details)
|
.with_details(details)
|
||||||
|
.with_hint(
|
||||||
|
// #778: stable remediation hint for automation
|
||||||
|
if !preflight.repo_exists || !preflight.worktree_exists {
|
||||||
|
"Ensure you are inside a git worktree (`git init` or `git worktree add`)."
|
||||||
|
} else if !missing_binaries.is_empty() {
|
||||||
|
"Install the listed missing required binaries."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
)
|
||||||
.with_data(Map::from_iter([(
|
.with_data(Map::from_iter([(
|
||||||
"boot_preflight".to_string(),
|
"boot_preflight".to_string(),
|
||||||
preflight.json_value(),
|
preflight.json_value(),
|
||||||
@@ -2964,6 +3005,16 @@ fn check_sandbox_health(status: &runtime::SandboxStatus) -> DiagnosticCheck {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_details(details)
|
.with_details(details)
|
||||||
|
.with_hint(
|
||||||
|
// #778: stable remediation hint — sandbox degraded on non-Linux hosts is expected, not an error
|
||||||
|
if degraded && !status.supported {
|
||||||
|
"Sandbox namespace isolation requires Linux with `unshare`. On macOS/non-Linux hosts this warning is expected and can be ignored. Filesystem isolation is still active."
|
||||||
|
} else if degraded {
|
||||||
|
"Check that the `unshare` binary is available and the process has the required capabilities."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
)
|
||||||
.with_data(Map::from_iter([
|
.with_data(Map::from_iter([
|
||||||
("enabled".to_string(), json!(status.enabled)),
|
("enabled".to_string(), json!(status.enabled)),
|
||||||
("active".to_string(), json!(status.active)),
|
("active".to_string(), json!(status.active)),
|
||||||
|
|||||||
Reference in New Issue
Block a user