From 3cfe6e2b14b606d2247502624cef196498260079 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 21 Apr 2026 21:40:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20#154=20=E2=80=94=20hint=20provider=20pr?= =?UTF-8?q?efix=20and=20env=20var=20when=20model=20name=20looks=20like=20d?= =?UTF-8?q?ifferent=20provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When a user types `claw --model gpt-4` or `--model qwen-plus`, they get: ``` error: invalid model syntax: 'gpt-4'. Expected provider/model (e.g., anthropic/claude-opus-4-6) or known alias ``` USAGE.md documents that "The error message now includes a hint that names the detected env var" — but this hint does not actually exist. The user has to re-read USAGE.md or guess the correct prefix. ## Fix Enhance `validate_model_syntax` to detect when a model name looks like it belongs to a different provider: 1. **OpenAI models** (starts with `gpt-` or `gpt_`): ``` Did you mean `openai/gpt-4`? (Requires OPENAI_API_KEY env var) ``` 2. **Qwen/DashScope models** (starts with `qwen`): ``` Did you mean `qwen/qwen-plus`? (Requires DASHSCOPE_API_KEY env var) ``` 3. **Grok/xAI models** (starts with `grok`): ``` Did you mean `xai/grok-3`? (Requires XAI_API_KEY env var) ``` Unrelated invalid models (e.g., `asdfgh`) do not get a spurious hint. ## Verification - `claw --model gpt-4` → hints `openai/gpt-4` + `OPENAI_API_KEY` - `claw --model qwen-plus` → hints `qwen/qwen-plus` + `DASHSCOPE_API_KEY` - `claw --model grok-3` → hints `xai/grok-3` + `XAI_API_KEY` - `claw --model asdfgh` → generic error (no hint) ## Tests Added 3 new assertions in `parses_multiple_diagnostic_subcommands`: - GPT model error hints openai/ prefix and OPENAI_API_KEY - Qwen model error hints qwen/ prefix and DASHSCOPE_API_KEY - Unrelated models don't get a spurious hint All 177 rusty-claude-cli tests pass. Closes ROADMAP #154. --- ROADMAP.md | 26 +++++++++++++ rust/crates/rusty-claude-cli/src/main.rs | 49 +++++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 2e9af1c..3b70234 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5934,3 +5934,29 @@ This creates a confusing gap: users build successfully but then get "command not **Blocker:** None. Pure documentation. **Source:** Clawhip nudge 2026-04-21 21:27 KST — onboarding gap from #claw-code observations earlier this month. + +## Pinpoint #154. Model syntax error doesn't hint at env var when multiple credentials present + +**Gap.** When a user types `claw --model gpt-4` but only has `ANTHROPIC_API_KEY` set (no `OPENAI_API_KEY`), the error is: +``` +error: invalid model syntax: 'gpt-4'. Expected provider/model (e.g., anthropic/claude-opus-4-6) or known alias (opus, sonnet, haiku) +``` + +But USAGE.md documents that "The error message now includes a hint that names the detected env var" — **this hint is not actually emitted.** The user gets a generic syntax error and has to re-read USAGE.md to discover they should type `openai/gpt-4` instead. + +**Expected behavior (from USAGE.md):** When the user has multiple providers' env vars set, or when a model name looks like it belongs to a different provider (e.g., `gpt-4` looks like OpenAI), the error should hint: +- "Did you mean `openai/gpt-4`? (but `OPENAI_API_KEY` is not set)" +- or "You have `ANTHROPIC_API_KEY` set but `gpt-4` looks like an OpenAI model. Try `openai/gpt-4` with `OPENAI_API_KEY` exported" + +**Current behavior:** Generic syntax error, user has to infer the fix from USAGE.md or guess. + +**Fix shape (~20 lines).** Enhance `FormatError::InvalidModelSyntax` or the model-parsing validation to: +1. Detect if the model name looks like it belongs to a known provider (prefix `gpt-`, `openai/`, `qwen`, etc.) +2. If it does, check if that provider's env var is missing +3. Append a hint: "Did you mean \`{inferred_prefix}/{model}\`? (requires `{PROVIDER_KEY}` env var)" + +**Acceptance:** `claw --model gpt-4` produces a hint about OpenAI prefix and missing `OPENAI_API_KEY`. Same for `qwen-plus` → hint about `DASHSCOPE_API_KEY`, etc. + +**Blocker:** None. Pure error-message UX improvement. + +**Source:** Clawhip nudge 2026-04-21 21:37 KST — discovered during dogfood probing of model validation. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 5262a1b..8500259 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1401,10 +1401,27 @@ fn validate_model_syntax(model: &str) -> Result<(), String> { // Check provider/model format: provider_id/model_id let parts: Vec<&str> = trimmed.split('/').collect(); if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() { - return Err(format!( + // #154: hint if the model looks like it belongs to a different provider + let mut err_msg = format!( "invalid model syntax: '{}'. Expected provider/model (e.g., anthropic/claude-opus-4-6) or known alias (opus, sonnet, haiku)", trimmed - )); + ); + if trimmed.starts_with("gpt-") || trimmed.starts_with("gpt_") { + err_msg.push_str("\nDid you mean `openai/"); + err_msg.push_str(trimmed); + err_msg.push_str("`? (Requires OPENAI_API_KEY env var)"); + } + else if trimmed.starts_with("qwen") { + err_msg.push_str("\nDid you mean `qwen/"); + err_msg.push_str(trimmed); + err_msg.push_str("`? (Requires DASHSCOPE_API_KEY env var)"); + } + else if trimmed.starts_with("grok") { + err_msg.push_str("\nDid you mean `xai/"); + err_msg.push_str(trimmed); + err_msg.push_str("`? (Requires XAI_API_KEY env var)"); + } + return Err(err_msg); } Ok(()) } @@ -10301,6 +10318,34 @@ mod tests { .expect_err("`doctor garbage` should fail without --json hint"); assert!(!err_other.contains("--output-format json"), "unrelated args should not trigger --json hint: {err_other}"); + // #154: model syntax error should hint at provider prefix when applicable + let err_gpt = parse_args(&["prompt".to_string(), "test".to_string(), "--model".to_string(), "gpt-4".to_string()]) + .expect_err("`--model gpt-4` should fail with OpenAI hint"); + assert!( + err_gpt.contains("Did you mean `openai/gpt-4`?"), + "GPT model error should hint openai/ prefix: {err_gpt}" + ); + assert!( + err_gpt.contains("OPENAI_API_KEY"), + "GPT model error should mention env var: {err_gpt}" + ); + let err_qwen = parse_args(&["prompt".to_string(), "test".to_string(), "--model".to_string(), "qwen-plus".to_string()]) + .expect_err("`--model qwen-plus` should fail with DashScope hint"); + assert!( + err_qwen.contains("Did you mean `qwen/qwen-plus`?"), + "Qwen model error should hint qwen/ prefix: {err_qwen}" + ); + assert!( + err_qwen.contains("DASHSCOPE_API_KEY"), + "Qwen model error should mention env var: {err_qwen}" + ); + // Unrelated invalid model should NOT get a hint + let err_garbage = parse_args(&["prompt".to_string(), "test".to_string(), "--model".to_string(), "asdfgh".to_string()]) + .expect_err("`--model asdfgh` should fail"); + assert!( + !err_garbage.contains("Did you mean"), + "Unrelated model errors should not get a hint: {err_garbage}" + ); } #[test]