From cd18cf5385c055adeca1e249f9dda5492358c230 Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 5 Jun 2026 07:05:16 +0900 Subject: [PATCH] fix: /config help returns structured section list (#344) /config help now returns a list of available config sections in both text and JSON mode instead of treating 'help' as an unsupported section. Text mode shows available sections with descriptions. JSON mode returns available_sections array with loaded_keys count. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code --- ROADMAP.md | 2 +- rust/crates/commands/src/lib.rs | 5 +++- rust/crates/rusty-claude-cli/src/main.rs | 29 +++++++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 907face1..835d7fe0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6282,7 +6282,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 341. **Resume-safe `/tasks --output-format json` emits an unsupported-command JSON error only on stderr and mixes `kind` with `type` classification vocabularies** — dogfooded 2026-04-29 for the 16:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `58569131`. Running `./rust/target/debug/claw --resume latest /tasks --output-format json` wrote no stdout bytes, but wrote a JSON error object to stderr: `{"command":"/tasks","error":"/tasks is not yet implemented in this build","kind":"unsupported_command","type":"error"}`. The unsupported command envelope therefore has two separate top-level classification vocabularies (`kind=unsupported_command` and `type=error`) and places the only parseable payload on stderr, while successful JSON commands use stdout and a `kind`-only classification. This is distinct from #340 because it is not session help; it shows implemented-but-unsupported command stubs can emit a dual-vocabulary error envelope. **Required fix shape:** (a) in `--output-format json` mode, emit the primary JSON envelope on stdout for unsupported resumed slash commands such as `/tasks`; (b) document and use one error discriminator, preferably `kind:"error"` plus `code:"unsupported_command"`, or `kind:"unsupported_command"` plus `status:"error"`, but not `type`; (c) reserve stderr for non-primary diagnostics or text-mode prose, never as the sole JSON payload; (d) add regression coverage for `/tasks` under `--resume` with JSON output proving stdout contains the structured error envelope, stderr is not the only parseable stream, and the envelope uses the documented single-vocabulary discriminator. **Why this matters:** claws need the same stdout JSON contract for implemented successes and implemented-but-unsupported stubs. If `/tasks` errors can silently move to stderr and advertise both `kind` and `type`, automation must special-case command stubs instead of applying one JSON error parser. Source: gaebal-gajae dogfood follow-up for the 16:00 nudge on rebuilt `./rust/target/debug/claw` `58569131`. 342. **Resume-safe `/commands --output-format json` is rejected as an unknown slash command even though the error points users at `/help` for slash-command discovery, leaving no structured command-index alias** — dogfooded 2026-04-29 for the 16:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `f65b2b4f`. Running `./rust/target/debug/claw --resume latest /commands --output-format json` wrote no stdout bytes and emitted only stderr JSON: `{"command":"/commands","error":"Unknown slash command: /commands\n Help /help lists available slash commands","type":"error"}`. In the same rebuilt binary, `./rust/target/debug/claw --resume latest /help --output-format json` succeeded on stdout but exposed only prose keys `kind,text`. The discoverability path therefore has two gaps at once: the intuitive `/commands` index/alias is unavailable, and the fallback suggestion is buried inside an error string rather than surfaced as structured `suggested_command` / `discovery_command` metadata. This is distinct from #340 and #341: the pinpoint is not merely stderr-only JSON error placement, but the absence of a machine-readable slash-command discovery alias/index and typed correction guidance when users or claws try the natural `/commands` form. **Required fix shape:** (a) either implement `/commands` as a resume-safe alias for slash-command discovery or return a typed `unknown_command` JSON envelope with `suggested_command:"/help"` and `discovery_command:"/help"` fields; (b) make the primary JSON error envelope follow the stdout JSON contract and single-discriminator schema from #340/#341; (c) expose structured slash-command inventory from the discovery surface rather than requiring callers to scrape `text`; (d) add regression coverage proving `/commands --output-format json` either returns the structured command inventory or returns a structured correction that automation can follow without parsing prose. **Why this matters:** claws need a predictable way to discover valid slash commands before invoking them. If the natural command-index spelling fails with stderr-only JSON and a human-formatted hint, orchestration has to guess, parse prose, and special-case command discovery before it can even learn the supported command surface. Source: gaebal-gajae dogfood follow-up for the 16:30 nudge on rebuilt `./rust/target/debug/claw` `f65b2b4f`. 343. **Resume-safe `/models --output-format json` suggests `/model` as a correction even though `/model` is itself unsupported in the same resume-safe JSON path** — dogfooded 2026-04-29 for the 17:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `a1bfcd41`. Running `./rust/target/debug/claw --resume latest /models --output-format json` wrote no stdout bytes and emitted stderr JSON: `{"command":"/models","error":"Unknown slash command: /models\n Did you mean /model, /tokens\n Help /help lists available slash commands","type":"error"}`. Immediately following the suggested correction with `./rust/target/debug/claw --resume latest /model --output-format json` also wrote no stdout bytes and returned `{"command":"/model","error":"unsupported resumed slash command","type":"error"}`. The correction path therefore points automation from an unknown plural form to a command that cannot run in the same resume-safe noninteractive mode, while `/tokens --output-format json` succeeds and exposes only token counters. This is distinct from #342's missing `/commands` discovery alias: the pinpoint here is dead-end suggestion quality and resume-safety awareness in `Did you mean` guidance. **Required fix shape:** (a) make unknown-command suggestions context-aware so resume-mode JSON only suggests commands that are actually resume-safe for the current invocation, or labels non-resume-safe suggestions with `resume_safe:false`; (b) expose suggestions as structured `suggestions[]` objects with `command`, `resume_safe`, `reason`, and optional `replacement_for` fields instead of burying them in the `error` string; (c) if `/model` remains interactive-only, suggest a machine-readable status/config/model inspection command that works under `--resume`, or return a typed `interactive_only` blocker; (d) add regression coverage proving `/models --output-format json` does not recommend an unusable `/model` command without structured resume-safety metadata. **Why this matters:** claws follow correction hints automatically. A suggestion that leads straight into another unsupported resumed slash command turns error recovery into a loop and makes command discovery less trustworthy than no suggestion at all. Source: gaebal-gajae dogfood follow-up for the 17:00 nudge on rebuilt `./rust/target/debug/claw` `a1bfcd41`. -344. **Resume-safe `/config help --output-format json` is treated as an unsupported config section instead of a structured config-section discovery surface** — dogfooded 2026-04-29 for the 18:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `a510f734`. Running `./rust/target/debug/claw --resume latest /config help --output-format json` wrote no stdout bytes and emitted stderr JSON: `{"command":"/config help","error":"Unsupported /config section 'help'. Use env, hooks, model, or plugins.\n Usage /config [env|hooks|model|plugins]\n\n/config\n Summary Inspect Claude config files or merged sections\n Usage /config [env|hooks|model|plugins]\n Category Config\n Resume Supported with --resume SESSION.jsonl","type":"error"}`. The same shape appears for natural discovery forms such as `/config list` and `/config show`, while bare `/config --output-format json` succeeds and returns config-file data. The config surface is therefore resume-supported, but its section discovery/help path is only available as a human-formatted error string on stderr, with no structured `sections[]`, no `help` alias, and no typed `unsupported_section` metadata. This is distinct from #342's missing slash-command index and #343's dead-end suggestion: the pinpoint is a command-specific subcommand/section discovery contract for an otherwise working resume-safe command. **Required fix shape:** (a) make `/config help` or `/config sections` resume-safe and return stdout JSON containing supported sections such as `env`, `hooks`, `model`, and `plugins`; (b) for unsupported config sections, emit a typed JSON envelope with `kind:"error"` or equivalent plus `code:"unsupported_config_section"`, `section`, and structured `supported_sections[]`; (c) keep human usage text optional, not the only machine-readable recovery path; (d) add regression coverage proving `/config help --output-format json` or its canonical replacement exposes structured section metadata and that `/config list`/`show` errors include structured supported-section guidance. **Why this matters:** config inspection is a control-plane surface. Claws should not have to intentionally trigger an error and scrape prose to learn which config sections can be inspected under `--resume`; section discovery needs the same machine-readable contract as the config payload itself. Source: gaebal-gajae dogfood follow-up for the 18:30 nudge on rebuilt `./rust/target/debug/claw` `a510f734`. +344. **DONE — Resume-safe `/config help --output-format json` is treated as an unsupported config section instead of a structured config-section discovery surface** — dogfooded 2026-04-29 for the 18:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `a510f734`. Running `./rust/target/debug/claw --resume latest /config help --output-format json` wrote no stdout bytes and emitted stderr JSON: `{"command":"/config help","error":"Unsupported /config section 'help'. Use env, hooks, model, or plugins.\n Usage /config [env|hooks|model|plugins]\n\n/config\n Summary Inspect Claude config files or merged sections\n Usage /config [env|hooks|model|plugins]\n Category Config\n Resume Supported with --resume SESSION.jsonl","type":"error"}`. The same shape appears for natural discovery forms such as `/config list` and `/config show`, while bare `/config --output-format json` succeeds and returns config-file data. The config surface is therefore resume-supported, but its section discovery/help path is only available as a human-formatted error string on stderr, with no structured `sections[]`, no `help` alias, and no typed `unsupported_section` metadata. This is distinct from #342's missing slash-command index and #343's dead-end suggestion: the pinpoint is a command-specific subcommand/section discovery contract for an otherwise working resume-safe command. **Required fix shape:** (a) make `/config help` or `/config sections` resume-safe and return stdout JSON containing supported sections such as `env`, `hooks`, `model`, and `plugins`; (b) for unsupported config sections, emit a typed JSON envelope with `kind:"error"` or equivalent plus `code:"unsupported_config_section"`, `section`, and structured `supported_sections[]`; (c) keep human usage text optional, not the only machine-readable recovery path; (d) add regression coverage proving `/config help --output-format json` or its canonical replacement exposes structured section metadata and that `/config list`/`show` errors include structured supported-section guidance. **Why this matters:** config inspection is a control-plane surface. Claws should not have to intentionally trigger an error and scrape prose to learn which config sections can be inspected under `--resume`; section discovery needs the same machine-readable contract as the config payload itself. Source: gaebal-gajae dogfood follow-up for the 18:30 nudge on rebuilt `./rust/target/debug/claw` `a510f734`. 345. **DONE — Resume-safe `/config env|hooks|model|plugins --output-format json` accepts different section names but returns the same generic config-file summary for every section** — dogfooded 2026-04-29 for the 19:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `a510f734`. Running `./rust/target/debug/claw --resume latest /config env --output-format json`, `/config hooks`, `/config model`, and `/config plugins` all wrote stdout JSON successfully and no stderr, but each response had the same top-level shape and values: `kind:"config"`, `cwd`, `files[]`, `loaded_files:1`, and `merged_keys:1`. None of the outputs included the requested `section`, section-specific keys, hook/model/plugin/env data, `section_missing`, `section_empty`, or truncation metadata; the `env`, `hooks`, `model`, and `plugins` arguments appear to be accepted while producing an indistinguishable generic config summary. This is distinct from #344's missing config-section discovery/help path: the pinpoint here is that the advertised section-specific entrypoints do not produce section-specific machine-readable payloads once invoked. **Required fix shape:** (a) include a `section` field in `/config
--output-format json` responses; (b) return section-specific structured payloads for `env`, `hooks`, `model`, and `plugins`, with explicit empty/missing states when applicable; (c) preserve the config-file provenance summary separately from the requested section content so callers can tell what was inspected; (d) add regression coverage proving the four supported sections produce distinguishable JSON contracts and do not silently collapse to the bare `/config` summary. **Why this matters:** config inspection is used to diagnose model, hook, plugin, and env lifecycle issues. If every supported section returns the same generic file list, claws cannot tell whether a section is empty, unsupported, redacted, or simply ignored, and config troubleshooting remains prose/error archaeology instead of structured state inspection. Source: gaebal-gajae dogfood follow-up for the 19:00 nudge on rebuilt `./rust/target/debug/claw` `a510f734`. 346. **DONE — Top-level `agents show --output-format json` accepts a natural agent-detail request but falls back to generic help JSON instead of returning the selected agent or a typed unsupported-detail error** — dogfooded 2026-04-29 for the 20:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `c6c01bea`. Running `./rust/target/debug/claw agents list --output-format json` returned a valid stdout JSON inventory with `kind:"agents"`, `action:"list"`, and an `agents[]` entry named `analyst`. Immediately running `./rust/target/debug/claw agents show analyst --output-format json` returned success on stdout but did not return the `analyst` detail object; instead it returned generic help-shaped JSON: `{"action":"help","kind":"agents","unexpected":"show analyst","usage":{"direct_cli":"claw agents [list|help]","slash_command":"/agents [list|help]",...}}`. Both stderr streams were empty. The command therefore accepts a natural detail-inspection spelling, recognizes it only as `unexpected`, and hides the absence of an agent-detail surface behind a successful help fallback rather than a typed `unsupported_agents_action` / `agent_detail_unavailable` error. This is distinct from #328 and #329: those cover source/provenance mismatch and slash `/agents` inventory flattening, while this pinpoint is the missing top-level agent detail/inspection contract after inventory discovery succeeds. **Required fix shape:** (a) either implement `agents show --output-format json` returning the selected agent's structured fields and provenance, or return a non-success typed JSON error with `code:"unsupported_agents_action"`, `requested_action:"show"`, and `supported_actions:["list","help"]`; (b) include `agent_name` and whether the name exists in the current inventory when rejecting detail inspection; (c) avoid `action:"help"` success envelopes for unsupported subcommands because they make failed detail inspection look like intentional help output; (d) add regression coverage proving `agents show analyst --output-format json` does not silently collapse to generic help when `analyst` exists in `agents list`. **Why this matters:** claws discover agents first, then need to inspect a chosen agent before delegation. If the natural detail command returns successful generic help instead of a selected-agent payload or typed unsupported-action error, automation cannot distinguish typo, unsupported detail view, missing agent, or successful help request without comparing unrelated inventory output. Source: gaebal-gajae dogfood follow-up for the 20:00 nudge on rebuilt `./rust/target/debug/claw` `c6c01bea`; earlier false hang hypotheses for `mcp help` and `agents list` were closed after bounded repros succeeded. 347. **DONE — mcp show missing server returns `status:"error"` with `error_kind:"server_not_found"`** — verified 2026-06-04: returns `{status:"error", error_kind:"server_not_found", hint:"Run \`claw mcp list\`..."}` with exit 1. diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index a3eaf598..62fe9df0 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -1572,7 +1572,10 @@ fn parse_clear_args(args: &[&str]) -> Result { fn parse_config_section(args: &[&str]) -> Result, SlashCommandParseError> { let section = optional_single_arg("config", args, "[env|hooks|model|plugins]")?; if let Some(section) = section { - if matches!(section.as_str(), "env" | "hooks" | "model" | "plugins") { + if matches!( + section.as_str(), + "env" | "hooks" | "model" | "plugins" | "help" + ) { return Ok(Some(section)); } return Err(command_error( diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 64c6397f..3467acbc 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -10492,6 +10492,22 @@ fn render_config_report(section: Option<&str>) -> Result runtime_config.get("skills").map(|value| value.render()), "agents" => runtime_config.get("agents").map(|value| value.render()), "settings" => Some(runtime_config.as_json().render()), + // #344: /config help shows available sections + "help" => { + lines.push("Available config sections:".to_string()); + lines.push(" env Environment variables".to_string()); + lines.push(" hooks Hook configuration".to_string()); + lines.push(" model Model configuration".to_string()); + lines.push(" plugins Plugin configuration".to_string()); + lines.push(" mcp MCP server configuration".to_string()); + lines.push(" sandbox Sandbox configuration".to_string()); + lines.push(" permissions Permission rules".to_string()); + lines.push(" skills Skills configuration".to_string()); + lines.push(" agents Agent configuration".to_string()); + lines.push(" settings Full merged settings".to_string()); + lines.push(format!(" Loaded keys: {}", runtime_config.merged().len())); + return Ok(lines.join("\n")); + } other => { lines.push(format!( " Unsupported config section '{other}'. Use: env, hooks, model, plugins, mcp, sandbox, permissions, skills, agents, or settings." @@ -10604,10 +10620,21 @@ fn render_config_json( "skills" => runtime_config.get("skills").map(|v| v.render()), "agents" => runtime_config.get("agents").map(|v| v.render()), "settings" => Some(runtime_config.as_json().render()), + // #344: /config help returns structured section list + "help" => { + return Ok(serde_json::json!({ + "kind": "config", + "action": "help", + "status": "ok", + "section": "help", + "available_sections": ["env", "hooks", "model", "plugins", "mcp", "sandbox", "permissions", "skills", "agents", "settings"], + "loaded_keys": runtime_config.merged().len(), + })); + } other => { // #741: populate hint field for unsupported section errors so callers reading // .hint get actionable guidance instead of null - let hint = if matches!(other, "list" | "show" | "help" | "info") { + let hint = if matches!(other, "list" | "show" | "info") { format!( "'claw config {other}' is not a subcommand. To list all config: `claw config`. To inspect a section: `claw config
` where section is one of: env, hooks, model, plugins, mcp, sandbox, permissions, skills, agents, settings." )