fix(onboard): classify expired API key as credential error (fixes #1942)#1944
fix(onboard): classify expired API key as credential error (fixes #1942)#1944kagura-agent wants to merge 1 commit into
Conversation
…DIA#1942) Gemini returns HTTP 400 with 'API key expired' for expired keys. classifyValidationFailure() checked HTTP 400 (model) before the credential message regex, so expired keys were never classified as credential failures — the user was looped back to provider selection without a chance to re-enter the key. Fix: move the credential message regex check before the HTTP 400 status check, and add 'api key expired' to the pattern. This ensures promptValidationRecovery offers retry/back/exit when the API key is expired or invalid.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughChanges extend error classification to recognize "API key expired" as a credential-related failure. The validation logic now detects this error message pattern, and corresponding test cases validate that expired API key responses are properly classified as credential-recovery failures. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
✨ Thanks for submitting this pull request, which proposes a way to fix the issue with expired API keys not being properly handled during onboarding. This change could help improve the user experience by providing clearer error messages and recovery options. Possibly related open issues: |
|
Closing to reduce PR volume. Apologies. |
…IDIA#1942) When a Gemini onboard validates with an expired API key, the provider returns HTTP 400 with the message "API key expired. Please renew the API key.". The current classifier sees httpStatus === 400 first and returns { kind: "model", retry: "model" }, so the expired-key case never reaches the credential-message regex. In the Gemini validation flow which does not allowModelRetry, kind: "model" falls through to unknown, and the onboard wizard loops back to provider selection without offering to re-enter the key. Fix: check credential-bearing error messages BEFORE the HTTP 400 model default, and extend the credential regex to include 'api key expired' and API_KEY_INVALID forms so Gemini's API_KEY_INVALID status classification also lands as credential. Preserves existing behavior: HTTP 400 without a credential-bearing message still classifies as model (regression guard test added). Tests - src/lib/validation.test.ts: 41 tests pass, +3 for the new paths (expired key, API_KEY_INVALID, regression guard) - src/lib/validation-recovery.test.ts: 5 tests pass Prior fix attempt (NVIDIA#1944) was closed by its author on 2026-04-18 to reduce PR volume. This patch replays the same core reorder with a slightly broader credential regex and preserves the existing test style. Closes NVIDIA#1942 Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
…IDIA#1942) When a Gemini onboard validates with an expired API key, the provider returns HTTP 400 with the message "API key expired. Please renew the API key.". The current classifier sees httpStatus === 400 first and returns { kind: "model", retry: "model" }, so the expired-key case never reaches the credential-message regex. In the Gemini validation flow which does not allowModelRetry, kind: "model" falls through to unknown, and the onboard wizard loops back to provider selection without offering to re-enter the key. Fix: check credential-bearing error messages BEFORE the HTTP 400 model default, and extend the credential regex to include 'api key expired' and API_KEY_INVALID forms so Gemini's API_KEY_INVALID status classification also lands as credential. Preserves existing behavior: HTTP 400 without a credential-bearing message still classifies as model (regression guard test added). Tests - src/lib/validation.test.ts: 41 tests pass, +3 for the new paths (expired key, API_KEY_INVALID, regression guard) - src/lib/validation-recovery.test.ts: 5 tests pass Prior fix attempt (NVIDIA#1944) was closed by its author on 2026-04-18 to reduce PR volume. This patch replays the same core reorder with a slightly broader credential regex and preserves the existing test style. Closes NVIDIA#1942 Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
## Summary
When a Gemini onboard validates with an expired API key, the provider
returns HTTP 400 with `"API key expired. Please renew the API key."`.
The current classifier checks `httpStatus === 400` first and returns `{
kind: "model", retry: "model" }`, so the expired-key case never reaches
the credential-message regex. In the Gemini validation flow (no
`allowModelRetry`), `kind: "model"` falls through to `unknown`, and the
onboard wizard loops back to provider selection without offering to
re-enter the key.
## Related Issue
Closes #1942
## Changes
- `src/lib/validation.ts` — move the credential-bearing message check
**before** the `httpStatus === 400 → model` default. Extend the
credential regex to include `api key expired` and `api[_ ]key[_
]invalid` so Gemini's `API_KEY_INVALID` status classification also lands
as `credential`.
- Inline comment documents why the order matters and cites #1942.
- HTTP 400 without a credential-bearing message still classifies as
`model` — regression guard test added.
## Testing
- [x] `src/lib/validation.test.ts` — 41 tests pass (+3 new: expired key,
API_KEY_INVALID, regression guard)
- [x] `src/lib/validation-recovery.test.ts` — 5 tests pass
- [x] Build + typecheck clean
Executed:
- Targeted `npx vitest run src/lib/validation.test.ts
src/lib/validation-recovery.test.ts` in the `nemoclaw-test` Docker
environment
## Prior art
Prior fix attempt [#1944](#1944)
was closed by its author on 2026-04-18 to reduce PR volume — the issue
remained open. This PR replays the same core reorder with a slightly
broader credential regex (covering both `API key expired` and
`API_KEY_INVALID` forms Gemini uses) and preserves the existing test
style.
## Checklist
- [x] Follows [Conventional
Commits](https://www.conventionalcommits.org/)
- [x] Commits are signed (SSH)
- [x] DCO Signed-off-by trailer present
Signed-off-by: latenighthackathon
<latenighthackathon@users.noreply.github.com>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Error classification now prioritizes credential-related messages so
API key/credential failures (expired, invalid, unauthorized) are
reported as credential issues rather than being misclassified as model
errors, improving clarity and retry guidance.
* **Tests**
* Added tests covering credential-related 400 responses and a regression
case to ensure unrelated model errors remain classified correctly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Problem
When a user enters an expired Google Gemini API key during onboard, the wizard loops back to provider selection without offering to re-enter the key (#1942).
Root cause:
classifyValidationFailure()checks HTTP status 400 →kind: "model"before checking the response message. Gemini returns HTTP 400 with"API key expired. Please renew the API key.", so the credential message regex never gets a chance to match.In the validation flow for Gemini (which uses
validateOpenAiLikeSelectionwithoutallowModelRetry),kind: "model"falls through to{ kind: "unknown", retry: "selection" }ingetProbeRecovery(), skipping the key re-entry prompt.Fix
classifyValidationFailure()api key expiredto the credential regex patternThis ensures expired/invalid API keys are classified as
kind: "credential"regardless of HTTP status code, sopromptValidationRecovery()correctly offers retry/back/exit.Changes
src/lib/validation.ts— reorder checks: credential message regex before HTTP 400src/lib/validation.test.ts— add tests for HTTP 400 + expired key message, and HTTP 400 + unrelated messagesrc/lib/validation-recovery.test.ts— add probe-level test for expired key recoveryTesting
All 45 validation + recovery tests pass. The 5 pre-existing preflight failures (detecting running gateway) are unrelated.
Summary by CodeRabbit