Skip to content

feat(desktop): add Get a new API Key and refresh stale keys at startup - #296

Merged
quickbeard merged 4 commits into
mainfrom
feat/gateway-key-refresh
Aug 22, 2026
Merged

quickbeard merged 4 commits into
mainfrom
feat/gateway-key-refresh

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Problem

The app fetches a gateway API key once, at first sign-in, and has no way to ever fetch another. DialogConfigure — which owns the whole SSO flow — has exactly one caller: GatewayConfigureGate, which opens only when no provider is connected. An expired key is still a connected provider, so the gate stays shut: every message 401s while the app considers itself signed in, and the sign-in flow is unreachable.

Meanwhile @codev/gateway already exported a tested validateApiKey (the same /key/info check codevhub uses) with zero production callers — the desktop only ever checked that a key exists, never that it works.

What this adds

1. A startup key check that heals itself. New ensureKey() on the gateway SSO controller, modelled on codevhub's ensureFreshGatewayKey:

  • validate the stored key against /key/info, raced against a 2.5s timeout so a slow gateway never delays startup;
  • only a definite 401/403 acts — true and "couldn't tell" both leave the key alone, so an unreachable gateway never signs anyone out;
  • on rejection, try silentSso + fetchApiKey and re-save the record with every other field intact (model, provider identity — dropping them would silently re-label a manually named provider);
  • hand the credential back to the renderer, which merges it into the local server's auth registry the way the configure flow does, since only the renderer writes to the server;
  • ask for a login only when there is no key, or the session is too old to re-mint.

The renderer runs it once per window after sync settles, opening DialogConfigure on needs-login.

2. A manual way in. A "Get a new API Key..." item dispatching a new gateway.apiKey.new command:

platform location
macOS CoDev menu, under Settings
Windows / Linux File menu, under Settings (platforms: ["windows"])
both command palette

That split is what Settings itself already does — Windows/Linux render no app menu, so app-level items get rehomed to File. Because menu items dispatch through the command registry, registering the command gets the palette entry for free.

Verification

  • bun typecheck clean in packages/app, packages/desktop, packages/codev-gateway; full bun turbo typecheck green via the pre-push hook.
  • Six new controller tests covering every ensureKey branch: valid key untouched; unreachable gateway untouched (and no refresh attempted); rejected key re-minted with the rest of the record preserved; no-session and no-key both asking for login; and the saved gateway URL used when an SSO-minted key records no base_url. bun test in packages/desktop: 14 pass in that file.
  • bun run test:unit in packages/app: 690 pass. bun test in packages/codev-gateway: 39 pass.
  • Menu model asserted per platform: exactly one entry each, CoDev > Get a new API Key... on macOS and File > Get a new API Key... on Windows/Linux.
  • Ran the real desktop app and temporarily instrumented ensureKey, confirming the whole chain fires end to end — renderer → IPC → controller → a live /key/info round-trip → true → silent no-op:
    [keydiag] ensureKey invoked; hasKey=true
    [keydiag] validate -> true
    
    The probe was removed before committing.

Not verified in the GUI: the menu item and the needs-login dialog. Screen capture is blocked in my environment and the app's CDP endpoint is unreachable, and forcing the rejected-key path would have meant tampering with your real credentials. The branch logic is unit-tested and the wiring is confirmed live, but the two visual surfaces are worth a manual click before merging.

DIVERGENCES row added; CHANGELOG entry under Desktop / Web UI.

🤖 Generated with Claude Code

quickbeard and others added 4 commits August 22, 2026 16:15
DialogConfigure had one caller, the configure gate, which fires only
when no provider is connected — so a gateway key the backend had since
rejected left the sign-in flow unreachable while every message 401'd.

Add ensureKey() to the gateway SSO controller, the desktop twin of
codev-hub's ensureFreshGatewayKey: validate against /key/info bounded by
a 2.5s race, silently re-mint via silentSso when rejected, and ask for a
login only when that is impossible. Add a Get a new API Key... menu item
(macOS app menu / Windows File menu, beside Settings) dispatching a new
gateway.apiKey.new command, which also lands in the command palette.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DialogConfigure renders hideClose and marks itself persistent, which is
right for the configure gate but traps anyone who opens it from the new
menu command or the startup key check — a backdrop click no longer
closes anything since the dialog rework. Gate dismissability on a prop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quickbeard
quickbeard force-pushed the feat/gateway-key-refresh branch from c0b41c1 to 3cef4b7 Compare August 22, 2026 09:16
@quickbeard
quickbeard merged commit b3a6eec into main Aug 22, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the feat/gateway-key-refresh branch August 22, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant