fix: add prefix-aware matching to config key suggestion

When input is a prefix of a candidate (e.g., mcp → mcpServers), return
the prefix match directly instead of relying on edit-distance which
would incorrectly suggest env (distance 3) over mcpServers (distance 7).

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
bellman
2026-06-05 01:36:35 +09:00
parent 9bc2f3631d
commit 4d4d72cd49
2 changed files with 12 additions and 1 deletions

View File

@ -6593,7 +6593,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
Five-line fix: change `spec.name == command_name` to `spec.name == command_name || spec.aliases.contains(&command_name)`, and use `spec.name` (not `command_name`) when formatting the guidance string so the suggestion always points at the canonical command. Source spec data already exists in `rust/crates/commands/src/lib.rs` — line 248 (`SlashCommandSpec { name: "skills", aliases: &["skill"], … }`), line 232 (`name: "plugin", aliases: &["plugins", "marketplace"]`), line 535 (`name: "approve", aliases: &["yes", "y"]`), line 542 (`name: "deny", aliases: &["no", "n"]`), line 689 (`name: "workspace", aliases: &["cwd"]`). The data is correct; only the lookup helper is wrong. **Why distinct from #119, #127, #117, #108:** #119 covers `claw hooks --help` (bare verb + extra-arg → billable) — `rest.len() != 1` gating. #127 covers `claw <verb> <ANY-EXTRA-ARG>` falling through to Prompt for diagnostic verbs (`doctor`, `status`, `sandbox`) — also extra-arg corruption. #117 covers `-p` greedy positional swallow. #108 covers subcommand typos falling through (`claw doctorr`). **None of these cover slash-command ALIAS lookup failing the canonical-name comparison.** The bug here is even worse than #108: in #108 the unknown verb is genuinely unknown to the system; in #460 the verb IS known (it's a documented alias listed in `--help` as `aliases: /yes, /y` for `/approve`) but the lookup helper for the bare-verb-on-CLI path doesn't consult the alias field. So **the system silently disagrees with itself**: `--help` advertises `/yes` as a valid approval slash command, but `claw yes` (the obvious "use that command from CLI" attempt) burns LLM tokens with no warning. **Why this matters:** (1) **`claw yes`, `claw no`, `claw cwd`, `claw marketplace` burn billable tokens with provider keys configured.** Same silent-token-burn pathology as #108 and #117, but specifically for documented slash-command aliases. (2) **`/help` output explicitly advertises these aliases:** `/skills [list|install <path>|help|<skill> [args]] List, install, or invoke available skills (aliases: /skill)` — and `/approve Approve a pending tool execution (aliases: /yes, /y)` and `/deny Deny a pending tool execution (aliases: /no, /n)`. A user reads `--help`, sees `/yes` exists, types `claw yes` to try the CLI invocation, and gets a credentials error or token burn. **Documentation contract violated.** (3) **The "did you mean" suggestions for the alias-fallthrough cases (`y`/`n`) are nonsense.** `claw y` suggests `system-prompt` (no relationship). `claw n` suggests `init, agents, sandbox` (no relationship). The levenshtein-style ranker is matching prefix/substring with no understanding that `y` is an `/approve` alias. (4) **System self-inconsistency:** REPL slash dispatch correctly resolves `/skill → /skills` via the alias field at `commands/src/lib.rs:248`. Tab-completion (`slash_command_completion_candidates_with_sessions` at `main.rs:8256-8267`) correctly enumerates aliases. Only the **CLI bare-verb-to-slash-guidance helper** misses the alias field — the singular violator in a system that otherwise honors aliases consistently. (5) **`marketplace` is the worst case:** it's neither a typo of any subcommand nor a common shell word — it's specifically a documented `/plugin` alias, and `claw marketplace` is exactly what someone exploring "is there a marketplace command?" would type. Silent token burn with provider keys. **Required fix shape:** (a) **At `main.rs:1135`, extend the lookup:** `slash_command_specs().iter().find(|spec| spec.name == command_name || spec.aliases.contains(&command_name))`. (b) **When formatting the guidance string**, use `spec.name` for the `/<canonical>` suggestion (not the alias), so users are pointed at the canonical form: `"\`claw yes\` is a slash command. Start \`claw\` and run \`/approve\` inside the REPL."`. (c) **Optionally include the alias in the message** for clarity: `"\`claw yes\` is the \`/approve\` slash command alias. Start \`claw\` and run \`/approve\` inside the REPL."`. (d) **Add a `claw doctor` self-check** that enumerates `slash_command_specs()`, walks every `aliases[]` entry, and asserts that `bare_slash_command_guidance(alias)` returns `Some(_)` for each. This is a one-loop regression catch that prevents future alias additions from silently regressing. (e) **Regression coverage**: matrix table above as a parameterized test — for each row marked ❌, assert the actual response includes `"is a slash command"` and references the canonical command name. **Acceptance check (one-liner):** `for v in skill marketplace yes y no n cwd; do out=$(claw $v --output-format json </dev/null 2>&1 | head -1); echo "$out" | grep -q 'is a slash command' || echo "MISS: $v → $out"; done` should print no MISS lines. Source: gaebal-gajae dogfood follow-up for the 2026-05-24 10:30 Clawhip pinpoint nudge at message `1508054486134947951`.
461. **`.claw.json` / `.claw/settings.json` unknown-key validator uses pure-edit-distance ranking with threshold ≤3, which actively misleads users away from the canonical `mcpServers` key — `{ "mcp": { "servers": {} } }` (the VS Code MCP convention, also used by hermes_cli and many other tools) produces `unknown key "mcp" (line 2). Did you mean "env"?`. Edit-distance(`mcp`, `mcpServers`) = 7 (exceeds threshold), edit-distance(`mcp`, `env`) = 3 (at threshold), so the validator hands the user a "go configure environment variables" hint instead of "you wrote the VS Code-style nested form, write the flat `mcpServers` form instead." User follows the suggestion → no MCP servers configured → no warning that MCP was their actual intent → silent loss of functionality with an actively wrong remediation path** — dogfooded 2026-05-24 for the 12:00 Clawhip pinpoint nudge at message `1508077133459751073` (also covers the 11:30 nudge `1508069585461706783`), reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`). Suggestion-quality matrix in clean isolated env (`HOME=/tmp/iso15/home`, fresh `/tmp/iso15/proj` git-init, `.claw/settings.json` with one top-level typo each):
461. **DONE — `suggest_field` now uses prefix-aware matching** — fixed 2026-06-04 in `fix: add prefix-aware matching to config key suggestion`. When the input is a prefix of a candidate (e.g., "mcp" → "mcpServers"), it returns the prefix match directly, avoiding edit-distance misranking that would suggest "env" instead.
| Input | Edit distance to `mcpServers` | Edit distance to `env` | Actual suggestion | Correct? |
|---|---|---|---|---|

View File

@ -475,6 +475,17 @@ fn validate_hook_entry_format(
fn suggest_field(input: &str, candidates: &[&str]) -> Option<String> {
let input_lower = input.to_ascii_lowercase();
// #461: prefix-aware matching — if input is a prefix of a candidate,
// treat it as distance 0 (perfect prefix match) to avoid edit-distance
// misranking (e.g., "mcp" → "env" instead of "mcpServers").
let prefix_match = candidates
.iter()
.filter(|c| c.to_ascii_lowercase().starts_with(&input_lower))
.min_by_key(|c| c.len())
.map(|name| name.to_string());
if prefix_match.is_some() {
return prefix_match;
}
candidates
.iter()
.filter_map(|candidate| {