Skip to content

AUTH-6735: retry/recovery for Unauthorized dashboard config - #219

Open
nicknisi wants to merge 2 commits into
mainfrom
bosun/task-msh07tdh-1fj3
Open

AUTH-6735: retry/recovery for Unauthorized dashboard config#219
nicknisi wants to merge 2 commits into
mainfrom
bosun/task-msh07tdh-1fj3

Conversation

@nicknisi

@nicknisi nicknisi commented Aug 6, 2026

Copy link
Copy Markdown
Member

bosun task: AUTH-6735: retry/recovery for Unauthorized dashboard config

Task id: task-msh07tdh-1fj3
Shape: ship
Project: workos/cli

…nfig

The installer called the WorkOS API to configure dashboard settings
(redirect URI, CORS origin, homepage URL) and, on a 401 "Unauthorized",
dropped straight to "configure it manually" with no recovery path
(AUTH-6735). Worse, the 401 was string-matched from the error message,
so a bare "Unauthorized" body was not even classified correctly.

- Carry the HTTP status on a new DashboardConfigError so 401 is detected
  exactly.
- On 401, explain the likely cause (expired/revoked key, wrong
  environment) and offer recovery before giving up: re-authenticate via
  the OAuth device flow (fresh staging credentials) or paste a different
  API key, then retry the dashboard configuration with the new key.
- Bound recovery to 2 attempts; decline or repeated failure falls back
  to manual instructions that now list the exact settings and values to
  apply in the dashboard.
- Return the API key that succeeded so callers write the working key to
  env files instead of the rejected one (agent runner, state machine,
  go/dotnet/ruby integrations).
- Non-interactive modes (agent/CI/JSON) skip the prompt and get the
  explanation plus specific manual instructions.
@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

AUTH-6735

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds bounded recovery and retry behavior when dashboard auto-configuration receives a 401, then propagates credentials recovered through re-authentication across installer paths.

  • Adds interactive re-authentication, replacement-key, and manual-configuration recovery choices.
  • Returns the successful API key and paired client ID from dashboard configuration.
  • Updates shared, Go, Ruby, and .NET installer paths to use recovered credentials.
  • Adds tests for retries, recovery limits, credential pairing, manual fallback, and error handling.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported re-authentication credential-pairing issue is addressed by returning and propagating both credentials from the newly selected environment.

Important Files Changed

Filename Overview
src/lib/workos-management.ts Adds typed dashboard errors, bounded 401 recovery, re-authentication and replacement-key handling, credential-pair propagation, and detailed manual fallback.
src/lib/run-with-core.ts Propagates recovered API keys and client IDs into the installer machine context and generated environment configuration.
src/lib/agent-runner.ts Updates the shared agent installer to continue with credentials returned by successful dashboard recovery.
src/integrations/go/index.ts Adopts recovered credentials before writing the Go environment file.
src/integrations/ruby/index.ts Adopts recovered credentials and includes the selected client ID in the Ruby agent instructions.
src/integrations/dotnet/index.ts Adopts recovered credentials before constructing the .NET installation prompt.
src/lib/workos-management.spec.ts Covers successful configuration, repeated and bounded recovery, paired credentials, pasted keys, manual fallback, and typed errors.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Configure dashboard with current API key] --> B{Request succeeds?}
  B -->|Yes| C[Return successful credentials]
  B -->|401| D{Recovery attempts remain?}
  B -->|Other error| H[Show manual configuration]
  D -->|No| H
  D -->|Yes| E{Recovery choice}
  E -->|Re-authenticate| F[Fetch paired API key and client ID]
  E -->|Paste API key| G[Use replacement key and warn about client ID]
  E -->|Manual or cancel| H
  F --> A
  G --> A
  C --> I[Write credentials and continue installer]
Loading

Reviews (2): Last reviewed commit: "fix: propagate clientId through 401 reco..." | Re-trigger Greptile

Comment thread src/lib/workos-management.ts

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

Open in Devin Review

Comment thread src/lib/agent-runner.ts Outdated
Comment on lines +78 to +79
// If 401 recovery re-authenticated, continue with the key that worked.
if (outcome) apiKey = outcome.apiKey;

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.

🔴 After signing in again, projects are set up with a mismatched key and app identifier

Only the replacement API key is carried forward after recovery (apiKey = outcome.apiKey at src/lib/agent-runner.ts:79) while the previously fetched client identifier is left untouched, so the credentials written into the project belong to two different WorkOS environments.

Impact: Sign-in in the generated app fails because the app identifier and the key no longer belong to the same WorkOS environment.

Mechanism: re-auth returns a full credential pair but only the key is propagated

promptForUnauthorizedRecovery (src/lib/workos-management.ts:203-216) calls fetchStagingCredentials, which returns { clientId, apiKey }, persists both via saveStagingCredentials, but returns only staging.apiKey. AutoConfigOutcome (src/lib/workos-management.ts:24-32) likewise only exposes apiKey.

Every caller then combines the fresh key with the stale clientId obtained earlier from getOrAskForWorkOSCredentials:

  • src/lib/agent-runner.ts:89-100writeEnvLocal({ WORKOS_API_KEY: apiKey (fresh), WORKOS_CLIENT_ID: clientId (stale) })
  • src/integrations/go/index.ts:147-157 — same pattern for .env
  • src/lib/run-with-core.ts:332-340writeEnvLocal with fresh apiKey and credentials.clientId
  • src/integrations/dotnet/index.ts:186config.environment.getEnvVars(apiKey, clientId)

The same mismatch occurs on the "Enter a different API key" branch (src/lib/workos-management.ts:192-198), where the pasted key may belong to another environment entirely.

Prompt for agents
When dashboard auto-config recovers from a 401 by re-authenticating, the recovery path (promptForUnauthorizedRecovery in src/lib/workos-management.ts) obtains a complete credential pair from fetchStagingCredentials (clientId + apiKey) but only surfaces the apiKey through AutoConfigOutcome. All callers (src/lib/agent-runner.ts, src/lib/run-with-core.ts configureEnvironment, src/integrations/go|dotnet|ruby) then write the fresh apiKey together with the previously-obtained, now-stale clientId, producing a credential pair from two different WorkOS environments and breaking sign-in in the generated app. Consider having the recovery hook and AutoConfigOutcome carry an optional clientId (undefined for the pasted-key branch, where the caller should at least be warned that the client ID may no longer match), and have each caller use it when present.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/run-with-core.ts Outdated
Comment on lines +329 to +331
});
// If 401 recovery re-authenticated, write the key that worked.
if (outcome) apiKey = outcome.apiKey;

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.

🟡 Installer hands the rejected key to the code-generating agent even after recovery

The refreshed key is stored only in a local variable (apiKey = outcome.apiKey at src/lib/run-with-core.ts:331) and never written back to the shared installer state, so the later install step still runs with the key WorkOS already rejected.

Impact: The install step operates with an invalid key, so key-dependent work during installation can fail even though recovery appeared to succeed.

Mechanism: state machine context credentials are not updated after 401 recovery

In configureEnvironment (src/lib/run-with-core.ts:322-340) the recovered key is used for writeEnvLocal, but context.credentials.apiKey is unchanged. The subsequent runAgent actor builds agentOptions from credentials?.apiKey (src/lib/run-with-core.ts:348-355), so the framework installer receives the stale key. Because options.apiKey/options.clientId are set, callerHandledConfig is true in src/lib/agent-runner.ts:68, so no re-configuration or env rewrite happens, and the stale key is passed as workOSApiKey to initializeAgent (src/lib/agent-runner.ts:114-121). The .env.local on disk and the key given to the agent therefore disagree.

Prompt for agents
In src/lib/run-with-core.ts, the configureEnvironment actor recovers a fresh API key from autoConfigureWorkOSEnvironment's 401 recovery but only uses it locally for writeEnvLocal. The state machine context's credentials.apiKey stays stale, and the runAgent actor later passes credentials?.apiKey to the framework installer (which forwards it to initializeAgent as workOSApiKey). Consider returning the recovered key from the configureEnvironment actor and assigning it into the machine context (an output/assign on the invoke done transition) so downstream steps use the key that actually worked.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/workos-management.ts Outdated
Comment on lines +203 to +216
try {
const { ensureAuthenticated } = await import('./ensure-auth.js');
const auth = await ensureAuthenticated();
if (!auth.authenticated) return null;

const { getAccessToken, saveStagingCredentials } = await import('./credentials.js');
const token = getAccessToken();
if (!token) return null;

const { fetchStagingCredentials } = await import('./staging-api.js');
const staging = await fetchStagingCredentials(token);
saveStagingCredentials(staging);
ui.log.success('Re-authenticated with WorkOS');
return staging.apiKey;

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.

🔍 "Re-authenticate" may be a no-op when the OAuth token is still valid

The most common cause of a 401 here (staging API key revoked/rotated or belonging to another environment) does not necessarily invalidate the CLI's OAuth session. ensureAuthenticated returns { authenticated: true } immediately when the access token is unexpired (src/lib/ensure-auth.ts:74-78) without any interactive re-login, so this branch simply re-fetches staging credentials with the same token. If the server returns the same apiKey, the same-key guard at src/lib/workos-management.ts:333 skips the retry and the user falls through to manual instructions — after being shown a green "Re-authenticated with WorkOS" message, which is misleading. Worth confirming that the staging credentials endpoint provisions/rotates a usable key in that scenario; otherwise consider forcing a fresh login or messaging the no-change case.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

…smatch

Re-authentication during dashboard auto-config 401 recovery may select a
different WorkOS account or environment, but only the new API key was
surfaced while callers kept the original client ID — producing project
config with credentials from two different environments that fail auth
at runtime.

- AutoConfigOutcome now carries an optional clientId; the recovery hook
  returns { apiKey, clientId? } (clientId present only for re-auth)
- promptForUnauthorizedRecovery surfaces both staging credentials on
  re-auth, and warns on the pasted-key branch that the client ID may no
  longer match
- agent-runner, run-with-core, go, dotnet, and ruby integrations adopt
  the recovered clientId when present, falling back to the original
- run-with-core writes recovered credentials back to the shared machine
  context so runAgent hands the agent the working key, not the rejected
  one

Refs: AUTH-6735
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant