Skip to content

fix(onboard): classify expired API key as credential error - #2132

Merged
ericksoa merged 2 commits into
NVIDIA:mainfrom
latenighthackathon:fix/gemini-expired-key-classification
Apr 23, 2026
Merged

ericksoa merged 2 commits into
NVIDIA:mainfrom
latenighthackathon:fix/gemini-expired-key-classification

Conversation

@latenighthackathon

@latenighthackathon latenighthackathon commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

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 Gemini onboard process could not handle key expiration properly #1942.
  • HTTP 400 without a credential-bearing message still classifies as model — regression guard test added.

Testing

  • src/lib/validation.test.ts — 41 tests pass (+3 new: expired key, API_KEY_INVALID, regression guard)
  • src/lib/validation-recovery.test.ts — 5 tests pass
  • 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 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

Signed-off-by: latenighthackathon latenighthackathon@users.noreply.github.com

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.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bfa563e9-2186-49d8-8d4b-9e22c6deacbf

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc7dce and b3d7fa4.

📒 Files selected for processing (2)
  • src/lib/validation.test.ts
  • src/lib/validation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/validation.ts
  • src/lib/validation.test.ts

📝 Walkthrough

Walkthrough

classifyValidationFailure() now detects credential-related message patterns earlier and returns { kind: "credential", retry: "credential" } before the httpStatus === 400 model classification; tests were added to verify Gemini 400 messages classify as credential errors and to prevent regressions.

Changes

Cohort / File(s) Summary
Validation Logic Update
src/lib/validation.ts
Moved credential/auth regex check up in classifyValidationFailure() so credential-related messages (e.g., "API key expired", "API_KEY_INVALID", "API key not valid", "unauthorized", "forbidden", "invalid_auth", "permission") short-circuit and return { kind: "credential", retry: "credential" } before the httpStatus === 400 branch; removed the later fallback credential check.
Credential Error Classification Tests
src/lib/validation.test.ts
Added four Vitest cases: three assert Gemini-style HTTP 400 messages (expired/invalid/bare API key text) classify as credential failures; one control case asserts a non-credential 400 message still classifies as a model error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through code at break of day,
Sniffed the keys that went astray.
"Expired!" I cried, "let credentials lead" —
Now the wizard asks anew indeed.
A carrot for tests that keep bugs at bay 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(onboard): classify expired API key as credential error' directly addresses the main change: reclassifying expired API key errors from model errors to credential errors in the onboarding flow.
Linked Issues check ✅ Passed The pull request fully implements the objectives from issue #1942: detects expired/invalid Gemini API keys, classifies HTTP 400 credential messages as credential errors, reorders validation logic accordingly, and adds corresponding test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to the credential error classification logic in validation.ts and its test file, directly addressing the linked issue requirements with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/validation.ts`:
- Around line 41-43: The credential-detection regex used on the normalized
string (the /.../.test(normalized) expression in validation.ts) misses messages
like "API key not valid"; update that regex to include a pattern matching "api
key not valid" (and a snake/underscore variant if you want consistency with the
existing api[_ ]key[_ ]invalid pattern) so such messages are caught by the
credential branch rather than falling through to the model check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 72006602-5990-4589-a01e-9aaaee89a1a9

📥 Commits

Reviewing files that changed from the base of the PR and between 42d2a85 and 4dbc99c.

📒 Files selected for processing (2)
  • src/lib/validation.test.ts
  • src/lib/validation.ts

Comment thread src/lib/validation.ts Outdated
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR that proposes a fix for the issue where an expired API key is not properly classified as a credential error during the Gemini onboard process. The changes and testing you provided will help us review this further.


Possibly related open PRs:


Possibly related open issues:

@latenighthackathon

Copy link
Copy Markdown
Collaborator Author

Extended the credential regex to api key (expired|not valid) so the bare "API key not valid. Please pass a valid API key." wording classifies correctly when extracted without the API_KEY_INVALID status prefix. Added a matching test. Cheers!

@jyaunches
jyaunches self-requested a review April 22, 2026 15:41
@cv cv added the v0.0.23 label Apr 22, 2026
@brandonpelfrey

Copy link
Copy Markdown
Contributor

@latenighthackathon if you can in the future, please mark your fork/branches so that we can merge commits in. Currently we cannot merge in the latest main into your branch as it's marked protected. Thanks!

@latenighthackathon

Copy link
Copy Markdown
Collaborator Author

@brandonpelfrey thanks for the heads up — updated those permissions on the fork ruleset (flipped update_allows_fetch_and_merge to true across all branches). Update Branch should work now on any of my open PRs. Cheers!

@latenighthackathon
latenighthackathon force-pushed the fix/gemini-expired-key-classification branch from 24ee88a to 4bc7dce Compare April 22, 2026 17:18
@copy-pr-bot

copy-pr-bot Bot commented Apr 22, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@brandonpelfrey

Copy link
Copy Markdown
Contributor

/ok to test 4bc7dce

@jyaunches

Copy link
Copy Markdown
Contributor

Heads up — the pr-self-hosted test-e2e-sandbox failure was a runner environment issue, not your code. Fixed in #2294 (merged). Rebase onto main or push a new commit to pick up the fix and get a green run.

)

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 (#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 #1942

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Addresses CodeRabbit nit on #2132:

> Credential regex misses "API key not valid" wording. Line 41 patterns
> won't match messages like "API key not valid. Please pass a valid API
> key." unless API_KEY_INVALID is also present.

When NemoClaw extracts only the Gemini error .message field (without
the API_KEY_INVALID status prefix), the bare wording slipped through
to the httpStatus === 400 -> model default.

Extend the credential regex to also match "api key not valid":
  /api key (expired|not valid)|api[_ ]key[_ ]invalid|.../

Tests
- src/lib/validation.test.ts: 42 tests pass (+1 for the bare wording)

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
@latenighthackathon
latenighthackathon force-pushed the fix/gemini-expired-key-classification branch from 4bc7dce to b3d7fa4 Compare April 22, 2026 21:39
@latenighthackathon

Copy link
Copy Markdown
Collaborator Author

@iamsh4 @jyaunches thanks — rebased onto current main so test-e2e-sandbox picks up the #2294 runner fix. Cheers!

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean reorder — credential message check before the 400→model default. Regression guard test for non-credential 400s is the right call. LGTM.

@ericksoa
ericksoa merged commit c538022 into NVIDIA:main Apr 23, 2026
8 of 10 checks passed
@latenighthackathon
latenighthackathon deleted the fix/gemini-expired-key-classification branch April 23, 2026 11:42
@wscurran wscurran added area: providers Inference provider integrations and provider behavior bug-fix PR fixes a bug or regression and removed Provider: Gemini labels Jun 3, 2026
@wscurran wscurran added the NV QA Bugs found by the NVIDIA QA Team label Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: providers Inference provider integrations and provider behavior bug-fix PR fixes a bug or regression NV QA Bugs found by the NVIDIA QA Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gemini onboard process could not handle key expiration properly

7 participants