Skip to content

fix(gateway): land a refused key on manual entry with the gateway's reason - #354

Merged
quickbeard merged 1 commit into
mainfrom
key-refusal
Sep 10, 2026
Merged

quickbeard merged 1 commit into
mainfrom
key-refusal

Conversation

@quickbeard

@quickbeard quickbeard commented Sep 10, 2026 •

Copy link
Copy Markdown
Owner

Why

A user whose email sits on a sub-domain of an allowlisted one was refused by the gateway's key-minting endpoint. In CoDev Code the SSO configure flow handled that like a flaky network: withRetries spent three attempts on it, then the TUI quit with a generic "setup failed" and the desktop toast showed the raw nested 502/400 status chain. The refusal is a policy answer, and the manual API-key step already exists for exactly this situation.

The companion backend change (quickbeard/codev-backend#25) reports the refusal as 403 with code: "key_refused"; the hub side is quickbeard/codev-hub#266. This PR is the CoDev Code twin.

What

  • @codev/gateway: fetchApiKey throws a BackendError carrying the backend's status, reason and code; isKeyRefusal is the check callers branch on. Both exported.
  • TUI (dialog-configure.tsx, dialog-custom-provider.tsx): withRetries rethrows a refusal on the first attempt. The SSO flow routes it to the existing manual-key prompt with a warning toast carrying the gateway's own sentence plus "Retrying won't change this — enter an API key issued to you by hand", instead of failAndQuit.
  • Desktop (gateway-sso/controller.ts): same withRetries rule; fetchSetup returns { hasApiKey: false, keyRefused: <reason> } instead of throwing. GatewaySetupResult gains the optional keyRefused field (structured-clone safe, passes through the existing IPC unchanged).
  • App renderer (gateway/dialog-configure.tsx): the API-key step shows the reason via a new gateway.configure.apikey.refused string (en + es) when present, otherwise the existing "no key" copy.
  • CHANGELOG entry under Gateway. No DIVERGENCES row needed: every touched file is fork-only except the i18n dictionaries, whose existing row already covers the whole gateway.configure.* family.

Tests

  • @codev/gateway: fetchApiKey happy path, a 403 key_refused → BackendError with code/reason/message, and a plain 502 that is a BackendError but not a refusal (78 pass).
  • Desktop controller: a refusal is attempted exactly once and reported as keyRefused with the controller left in ready (23 pass).
  • Typecheck clean in codev-gateway, tui, desktop, app. Prettier clean on all changed files.

Rollout

Only takes effect against a backend that sends key_refused; against an older backend the previous behaviour stands. The reporting user is unblocked only by the gateway allowlist change or a key issued by hand.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DFzDyvc2uV1Vp1WoHBkiPB

…eason

When the CoDev gateway declines to issue a key for an account (an email
domain outside its allowlist, e.g. a sub-domain of a listed one), the
backend now answers 403 with code "key_refused". Both configure flows
treated that like a flaky network: withRetries spent three attempts on it,
then the TUI quit with a generic "setup failed" and the desktop toast showed
a nested 502/400 status chain.

fetchApiKey now throws a BackendError carrying the backend's reason and
code; isKeyRefusal is what callers branch on. withRetries (TUI and desktop)
rethrows a refusal on the first attempt. The TUI's SSO flow and the desktop
controller's fetchSetup route a refusal to the manual API-key step with the
gateway's own sentence and a note that retrying won't change it — the
desktop carries it as GatewaySetupResult.keyRefused, which the renderer's
API-key step renders via a new gateway.configure.apikey.refused string.

Hub parity: codev-hub's install step got the same handling.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFzDyvc2uV1Vp1WoHBkiPB
@quickbeard
quickbeard merged commit 8f5f946 into main Sep 10, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the key-refusal branch September 10, 2026 08:38
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