Skip to content

Don't discard a valid cloud key when the add-time model probe fails transiently - #5341

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5339-deepseek-key-save-mismatch
Aug 4, 2026
Merged

senamakel merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5339-deepseek-key-save-mismatch

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adding a cloud key provider (DeepSeek, OpenAI, etc.) no longer discards a valid API key when the add-time /models probe fails for a transient reason.
  • The add-time probe failure is now classified: a genuine auth failure (401 / invalid key) still rejects and rolls back; a non-auth failure (timeout / unreachable / unknown) keeps the key, persists the provider, and shows a non-fatal advisory.
  • Local runtimes (Ollama / LM Studio / OMLX) keep their original reject-on-probe behaviour — an unreachable runtime is a real setup error and holds no key.
  • Both rollback .catch(() => {}) swallows now log, so a failed key-clear can no longer orphan a key on disk silently.
  • Reuses the existing classifyProviderVerificationFailure / describeProviderVerificationFailure copy and the settings.ai.providerTest.* i18n family — no new i18n keys.

Problem

Closes #5339. When a user pasted a valid DeepSeek key and hit Save, connectProvider wrote the key (setCloudProviderKey), flushed the provider config, then probed openhuman.inference_list_models. On any probe failure it rolled the key back and threw Could not reach …, so:

  • A momentary /models hiccup (timeout, unreachable host, proxy) discarded a valid key and reported the save as failed. The built-in key dialog has no "add anyway" escape hatch (the custom-provider editor got one in [Bug] Azure Foundry: "Model not found" when deployment name differs from model name — allow free-text deployment name input #5213), so the provider was effectively unsaveable.
  • The rollback's clearCloudProviderKey(slug).catch(() => {}) swallowed its error. If the clear failed (most likely under the very transient conditions that failed the probe), the key stayed in auth-profiles.json with no config entry — an orphan that reads as "saved but not saved" and survives restarts.

Solution

In connectProvider's probe catch, classify the error with the existing classifyProviderVerificationFailure:

  • auth (401 / invalid key), or any local runtime: unchanged — roll both stores back and throw so the dialog shows the error and stays open.
  • Non-auth for a cloud key provider (!isLocalRuntime && slug !== 'openhuman'): do not roll back or throw. Keep the key, fall through to persist, and set a dismissible amber advisory (providerSaveNotice) rendered next to the provider list — "The key was saved, but '' …". The provider is usable once reachable; the user picks a model to route to it.

Both rollback .catch handlers now console.warn instead of swallowing.

Design notes / tradeoffs:

  • Scoped strictly to cloud key providers. Local runtimes deliberately keep reject-on-probe (no key to preserve; a wrong "the key was saved" message would be misleading).
  • An earlier draft also added a loadAISettings reconcile that re-materialised an orphaned builtin key on reload. It was dropped: the builtin chip toggle-OFF removes the config entry without clearing the key, so an intentional disconnect leaves the exact same on-disk state as a failed-add orphan — the reconcile could not distinguish them and would silently revert disconnects. Preventing the orphan at the source (this PR) is the correct fix; a pre-existing orphan now self-heals on re-add.

Submission Checklist

  • Tests added or updated (happy path + failure/edge case) — two connectProvider tests in AIPanel.test.tsx: a non-auth probe failure keeps the key + persists + shows the advisory (and is dismissible); an auth probe failure clears the key + rejects.
  • Diff coverage ≥ 80% — changed lines are at 100% line coverage (measured via Vitest v8 over the changed file). Full app Vitest suite green (9114 passed). pnpm test:rustN/A: no Rust changed.
  • Coverage matrix updated — N/A: behaviour-only change to an existing feature (provider connect); no feature row added/removed/renamed.
  • All affected feature IDs listed under ## RelatedN/A: no matrix feature row affected.
  • No new external network dependencies — N/A: no new network calls; the model-listing probe already existed.
  • Manual smoke checklist updated — N/A: no change to release-cut smoke steps; the provider-connect happy path is unchanged, only the transient-failure branch differs.
  • Linked issue closed via Closes #NNN — see ## Related.

Impact

  • Platform: desktop (Windows / macOS / Linux) — Settings → AI → provider connect. Frontend-only; no Rust, no RPC, no migration.
  • Behaviour: a transient add-time probe failure now results in a saved provider + advisory instead of a lost key + hard error. Auth failures and local runtimes are unchanged. No performance or security impact; no secrets logged (rollback logs carry only slug + error).

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/5339-deepseek-key-save-mismatch
  • Commit SHA: 011123c

Validation Run

  • pnpm --filter openhuman-app format:check — pass
  • pnpm typecheck — pass
  • Focused tests: pnpm test src/components/settings/panels/__tests__/AIPanel.test.tsx — 68 pass; full app unit suite — 9114 pass / 0 fail
  • Rust fmt/check (if changed): N/A: no Rust changed
  • Tauri fmt/check (if changed): N/A: no Rust changed

Validation Blocked

  • command: local pre-push hook (pnpm rust:clippy)
  • error: cargo clippy fails to resolve tinychannels on this host due to pre-existing vendored-submodule pointer drift in the working tree (unrelated to this PR); it also needs GGML_NATIVE=OFF for whisper-rs/llama.cpp on Apple Silicon. Bypassed with --no-verify as unrelated pre-existing breakage. Remote CI runs clippy against clean submodules.
  • impact: none on this change — it touches only two TypeScript files (zero Rust); format:check, lint (0 errors), typecheck, and lint:commands-tokens all pass locally.

Behavior Changes

  • Intended behavior change: a non-auth add-time probe failure for a cloud key provider no longer discards the key or blocks the save.
  • User-visible effect: the key is saved and the provider appears connected, with an amber "the key was saved, but the provider was unreachable" advisory instead of a "could not reach / not saved" error.

Parity Contract

  • Legacy behavior preserved: auth (401 / invalid key) failures and all local-runtime probe failures still roll back and reject exactly as before; codex_oauth / cli_login still skip the probe.
  • Guard/fallback/dispatch parity checks: isKeyProvider = !isLocalRuntime && slug !== 'openhuman' mirrors the original clearCloudProviderKey guard; the else branch reproduces the original rollback + throw.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Cloud providers with temporary connectivity or verification issues can now be saved for later use.
    • Added dismissible notices when a saved provider is currently unreachable.
    • Notices clear when verification is retried or a provider is removed.
  • Bug Fixes

    • Authentication failures, including forbidden or invalid-key responses, continue to prevent invalid provider settings from being saved.
    • Provider credentials are retained when verification fails for non-authentication reasons.
    • Local runtime failures continue to prevent invalid provider settings from being saved.

Adding a cloud key provider wrote the API key first, then probed
`/models`; on any probe failure it rolled the key back and threw
"Could not reach ...". A momentary probe hiccup (timeout, unreachable
host) therefore discarded a perfectly valid key and reported the save
as failed. The built-in key dialog has no "add anyway" escape hatch, so
the provider was effectively unsaveable — and because the rollback's
key-clear was best-effort with a swallowed error, a failed clear left
the key orphaned on disk, reading as "not saved" across restarts.

Classify the probe failure instead of treating every failure the same:

- Auth failure (401 / invalid key): the key itself is wrong, so keep the
  existing behavior — roll both stores back and reject so the user fixes
  it.
- Non-auth failure (timeout / unreachable / unknown) for a cloud key
  provider: the key is plausibly valid, so keep it, persist the
  provider, and surface a non-fatal amber "the key was saved, but the
  provider was unreachable" advisory rather than blocking the save.
- Local runtimes keep the original reject-on-probe behavior (an
  unreachable runtime is a real setup error and holds no key).

Both rollback catches now log instead of swallowing, so a failed
key-clear can no longer orphan a key silently.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 5 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e5f4abd-a83b-4aaf-9eab-8d2f7633c87b

📥 Commits

Reviewing files that changed from the base of the PR and between e082a79 and 34c2338.

📒 Files selected for processing (4)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/services/api/__tests__/aiSettingsApi.test.ts
  • app/src/services/api/aiSettingsApi.ts
📝 Walkthrough

Walkthrough

Provider verification now distinguishes authentication failures from other failures. Cloud keys and providers persist after non-authentication probe failures with a dismissible advisory. Authentication and local-runtime failures still roll back configuration and credentials.

Changes

Provider verification handling

Layer / File(s) Summary
Cloud provider save and advisory flow
app/src/services/api/aiSettingsApi.ts, app/src/services/api/__tests__/aiSettingsApi.test.ts, app/src/components/settings/panels/AIPanel.tsx, app/src/components/settings/panels/__tests__/AIPanel.test.tsx
HTTP 403 and Forbidden failures classify as authentication errors. Non-authentication probe failures retain and save cloud keys, then display a dismissible advisory. Authentication and local-runtime failures clear credentials and restore provider configuration. Tests cover these outcomes and rollback logging.
Custom provider rollback diagnostics
app/src/components/settings/panels/AIPanel.tsx
Rollback failures in the custom provider editor are logged while provider verification still fails.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsUI
  participant AIPanel
  participant ProviderAPI
  participant SettingsStorage
  SettingsUI->>AIPanel: submit provider credentials
  AIPanel->>ProviderAPI: probe /models
  ProviderAPI-->>AIPanel: verification result
  alt non-authentication failure
    AIPanel->>SettingsStorage: save provider and key
    AIPanel-->>SettingsUI: show saved-but-unreachable advisory
  else authentication or local-runtime failure
    AIPanel->>SettingsStorage: restore configuration and clear credentials
    AIPanel-->>SettingsUI: report setup failure
  end
Loading

Possibly related PRs

Suggested labels: bug

Suggested reviewers: senamakel

Poem

A rabbit probes the cloud API.
Non-auth failures let keys stay.
Auth errors roll settings back.
Amber notices mark the track.
Retry hops clear the warning away.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses probe handling and advisories but does not implement several #5339 requirements, including reconciliation, array-race protection, model selection, and Rust tests. Implement the remaining #5339 requirements or split them into linked follow-up PRs before considering the issue complete.
✅ 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 clearly describes the primary change: preserving valid cloud keys when transient model probes fail.
Out of Scope Changes check ✅ Passed The code and tests remain focused on provider probe classification, persistence advisories, authentication failures, and rollback diagnostics.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 3, 2026 14:31
@YellowSnnowmann
YellowSnnowmann requested a review from a team August 3, 2026 14:31
@coderabbitai coderabbitai Bot added the bug label Aug 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 011123c451

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/components/settings/panels/AIPanel.tsx

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 3676-3691: Add regression tests for the rollback handling around
flushCloudProviders and clearCloudProviderKey: mock the initial flush as
successful, make rollback flushing and credential cleanup fail, and assert both
failures are warned. Verify the probe still rejects and the warning path does
not persist the provider.
- Around line 3054-3072: Update
app/src/components/settings/panels/AIPanel.tsx:3054-3072 around
classifyProviderVerificationFailure so HTTP 403 and forbidden responses classify
as auth before the non-fatal credential-retention branch. Update
app/src/components/settings/panels/__tests__/AIPanel.test.tsx:62-66 to map 403
and forbidden mock responses to auth, and add a bare-403 regression case at
996-1010 asserting key cleanup and no settings save.
- Around line 3073-3087: Update the failure branch in
openhumanUpdateLocalAiSettings to snapshot and restore the affected local_ai
settings, including endpoint, provider, enablement flags, and OMLX API key, when
listProviderModels fails. Preserve the existing cloud_providers and key rollback
behavior, and add a regression test verifying rejected local probes leave
local_ai unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 14c6a848-c103-4e0b-86b8-34bb41f49e0c

📥 Commits

Reviewing files that changed from the base of the PR and between 2e98f39 and 011123c.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx

Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx
@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a data-loss bug where a valid cloud provider API key was silently discarded whenever the add-time /models probe failed transiently. It classifies probe failures into auth vs. non-auth: auth failures (401/403 credential) and local runtime failures keep their original reject-and-rollback behaviour; non-auth failures for cloud key providers now preserve the key, persist the provider, and show a dismissible amber advisory.

  • classifyProviderVerificationFailure gains a proxy/gateway early-exit guard (407, Cloudflare, bad gateway) to prevent network-level errors from being misclassified as credential failures, plus a 403+credential-word heuristic to catch revoked-key responses.
  • connectProvider splits the catch into a non-fatal path (keep key, set advisory keyed by slug) and the existing rollback path (flush + clear key + throw), with both rollback .catch arms now logging instead of swallowing.
  • providerSaveNotice state is slug-keyed so that toggling off an unrelated provider can never clear another provider's advisory, closing the gap identified in the previous review thread.

Confidence Score: 5/5

Safe to merge — the change is strictly scoped to the add-time probe error path, auth failures and local runtimes are unchanged, and the new advisory state is correctly slug-keyed throughout.

The core logic is correct: the proxy/gateway guard fires before the auth check so '407 Proxy Authentication Required' and Cloudflare 403s cannot accidentally delete a valid key; rollback paths log failures instead of swallowing them; and the slug-keyed advisory closes the cross-provider wipe found in the earlier review thread. Five new integration tests exercise the key branches including rollback-failure logging on both the builtin and custom-provider paths. No silent failure modes remain in the changed code.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
app/src/services/api/aiSettingsApi.ts Adds proxy/gateway early-exit guard and 403-credential heuristic to classifyProviderVerificationFailure; ordering and word-boundary regexes are correct
app/src/components/settings/panels/AIPanel.tsx Introduces slug-keyed providerSaveNotice state, non-fatal probe branch, amber advisory UI, and logged (non-swallowed) rollback handlers; all clear-points are scoped to the correct slug
app/src/components/settings/panels/tests/AIPanel.test.tsx Migrates mock to importOriginal to reuse real classifier/describer, adds five new tests covering non-auth keep, auth reject, 403 Forbidden, rollback-logging (builtin and custom paths)
app/src/services/api/tests/aiSettingsApi.test.ts Adds targeted classification test cases for 403-credential, proxy-WAF, bare 407, 502, and request-ID false-positive scenarios

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[connectProvider called] --> B[setCloudProviderKey]
    B --> C[flushCloudProviders with new provider]
    C --> D{isCodexOAuth or isCliLogin?}
    D -- Yes --> I[persist + close dialog]
    D -- No --> E[listProviderModels probe]
    E -- success --> I
    E -- failure --> F[classifyProviderVerificationFailure]
    F --> G{isKeyProvider AND reason != auth?}
    G -- Yes non-auth cloud key --> H[setProviderSaveNotice with slug+message]
    H --> I
    G -- No auth failure or local runtime --> J[flushCloudProviders rollback - logs on error]
    J --> K{isKeyProvider?}
    K -- Yes --> L[clearCloudProviderKey - logs on error]
    K -- No --> M[throw Could not reach ...]
    L --> M
Loading

Reviews (3): Last reviewed commit: "Harden probe classification and key the ..." | Re-trigger Greptile

Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/__tests__/AIPanel.test.tsx Outdated
…logs

Review follow-ups on the add-time provider fix:

- Classify HTTP 403 / Forbidden as `auth` in
  `classifyProviderVerificationFailure`. A revoked or permission-denied key
  surfaces as a bare 403 with no "401"/"unauthorized" text; without this it fell
  into the non-fatal branch and was kept behind a "key was saved" advisory
  instead of being rejected. Added classifier assertions and a bare-403
  connect regression (key cleared, no save, no advisory).

- Remove the provider-toggle-off advisory clears. `setProviderSaveNotice(null)`
  holds a single unscoped string, so toggling off provider B wiped an advisory
  that belonged to provider A. The advisory is already dismissible and is reset
  on the next connect attempt, so the unscoped clear did more harm than good.

- Add regression tests for the rollback-failure logging on both the built-in
  connect path and the custom-provider editor path: with the initial flush
  succeeding and both rollback legs failing, each failure is warned and the
  provider is not persisted.

- Make the test's `describeProviderVerificationFailure` stub reason-dependent so
  it reflects the actual code path (auth "rejected it" vs the non-auth "a test
  call failed" copy) instead of always returning the auth string.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 3, 2026

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

The direction is right. Making the add-time probe non-fatal for cloud key providers, keeping local runtimes reject-on-probe, and un-swallowing the two rollback .catch(() => {}) are all correct. The rejected loadAISettings reconcile is well-reasoned too — toggle-OFF really does leave the identical on-disk shape, so the reconcile could not have distinguished a disconnect from a failed-add orphan.

My concern is that the change makes error classification load-bearing, and the classifier is not precise enough to carry that weight.


1. Major — the new 403 / forbidden rule reopens the reported bug for proxied users

classifyProviderVerificationFailure (app/src/services/api/aiSettingsApi.ts:509) matches bare substrings against the whole error string. Two very common network-side rejections now land in auth and therefore delete a valid key:

  • 407 Proxy Authentication Required → matches the pre-existing authentication term.
  • A corporate proxy / Cloudflare / WAF 403 Forbidden sitting in front of the provider → matches the two terms this PR adds.

Neither says anything about the key itself. The PR description lists the triggers it is fixing as "timeout, unreachable host, proxy" — so the classifier now routes one of the named triggers straight into the destructive branch.

Before this PR, misclassification was harmless: every failure rolled back identically. Now reason === 'auth' is the difference between keeping the key and destroying it, so the loose matching has real consequences. Note also that haystack.includes('403') matches 1403, a request id that happens to contain 403, or 4032 bytes.

Codex and CodeRabbit both pushed for 403 → auth, and in isolation that is defensible for a revoked key. The gap is that the false-positive direction was not weighed — and that direction is the bug being fixed.

Suggested narrowing, before the auth branch:

// Gateway/proxy rejections are network-side — the key is untouched.
if (/\b407\b|proxy|cloudflare|bad gateway|gateway timeout/.test(haystack)) return 'unknown';

…and require 403 to co-occur with credential wording rather than matching anywhere in the string.


2. Medium — the "local runtimes hold no API key" premise is false for OMLX

The comment at app/src/components/settings/panels/AIPanel.tsx:3057 justifies rejecting local runtimes on the grounds that they hold no key to preserve. OMLX is credentialMode: 'endpoint_key'isLocalRuntime === true, and it does write a key:

await openhumanUpdateLocalAiSettings({
  base_url: endpoint, api_key: trimmed, provider: 'omlx', runtime_enabled: true, opt_in_confirmed: true,
});

The else branch rolls back flushCloudProviders only — local_ai is never reverted. So an OMLX add whose probe fails leaves local_ai.api_key plus runtime_enabled: true on disk with no provider entry: exactly the orphan class this PR exists to eliminate, left in place and justified by an incorrect premise.

The behaviour is unchanged from before, so it is not a regression from this PR — but the comment should be corrected either way, and it deserves a follow-up issue.


3. Minor — providerSaveNotice is a single un-keyed string

It is cleared only at the top of connectProvider and by Dismiss. Consequences:

  • Toggling the referenced provider OFF (AIPanel.tsx:3252) removes it without clearing the advisory — that path never calls connectProvider, so a stale notice keeps naming a provider that no longer exists. Same for onClearKey at :3709.
  • Connecting an unrelated provider clears an advisory about a different one.

Storing { slug, message } and rendering/clearing per slug fixes both. (The CodeRabbit release-note bullet "Notices clear when … a provider is removed" is not backed by the code.)


4. Nit — the test mock is a third copy of the auth rule

AIPanel.test.tsx:58 hand-reimplements the classifier regex across two mocks. The new 403 rule already had to be added in two places to keep them in sync. vi.importActual for these two pure functions would remove the drift risk entirely.


Happy to re-review once (1) is addressed — the rest are non-blocking.

Address maintainer review — the change made error classification decide
whether a key is kept or deleted, so the classifier has to be precise:

- Network/gateway/proxy rejections no longer delete a valid key. A guard
  ahead of the auth branch routes 407, `proxy`, `cloudflare`, `bad
  gateway`, and `gateway timeout` to `unknown` (non-destructive). A bare
  403 only counts as a rejected credential when it co-occurs with
  credential wording, and 401/403 now match on word boundaries so a
  request id like `1403` can't trip them. This closes the reopened
  data-loss path for corporate-proxy / WAF users (the "proxy" trigger the
  fix is meant to preserve keys through).

- Key the "key saved, but unreachable" advisory by slug. It was a single
  unscoped string, so connecting one provider or toggling off another
  wiped an unrelated provider's notice. It is now cleared only for the
  provider it belongs to, and provider removal / key-clear drops its own
  advisory.

- Correct the local-runtime comment: OMLX (`endpoint_key`) is a local
  runtime that does write a key (into `local_ai.api_key`), so the "holds
  no key" justification was wrong. Its `local_ai` write is not rolled
  back on a failed probe — pre-existing behaviour, noted for follow-up.

- Drop the hand-copied classifier from the AIPanel test; it now uses the
  real `classify`/`describe` via `importOriginal`, removing the drift the
  403 change had to update in three places.
@YellowSnnowmann

Copy link
Copy Markdown
Collaborator Author

Thanks — the false-positive direction is the right thing to weigh here, and it's addressed in 34c23384.

1 (Major) — proxy/gateway false positives. Fixed. classifyProviderVerificationFailure now short-circuits network-side rejections to unknown (non-destructive) before the auth branch: \b407\b, proxy, cloudflare, bad gateway, gateway timeout. A bare 403 only classifies as auth when it co-occurs with credential wording (forbidden/key/credential/permission), and 401/403 now use \b…\b so a request id like 1403 can't trip them. Added unit cases: 403 Forbidden (via Cloudflare), 407 Proxy Authentication Required, 502 Bad Gateway, and request id 1403 all resolve to unknown; 403: API key does not have permission stays auth.

2 (Medium) — OMLX premise. Corrected the comment: OMLX (endpoint_key) is a local runtime that does write a key, into local_ai.api_key. The local_ai write isn't rolled back on a failed local probe — pre-existing, unchanged here — and I've called it out in-code as a follow-up. Happy to file a separate issue for the local_ai snapshot/restore.

3 (Minor) — un-keyed advisory. Now { slug, message }. It's cleared only for the provider it belongs to: connecting/removing a different provider leaves it, and removing this provider (toggle-off, custom-toggle, or Clear key) drops it.

4 (Nit) — third copy of the classifier. Removed. The AIPanel test now pulls the real classify/describe via importOriginal, so there's no hand-copied regex to keep in sync.

All checks green locally (typecheck, lint, 100% diff coverage). Ready for another look.

@senamakel
senamakel merged commit fb2bd8e into tinyhumansai:main Aug 4, 2026
21 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Aug 4, 2026
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

DeepSeek API key is saved but the Settings dialog reports "not saved"; chat calls fail and the state persists across restarts

3 participants