moeru-ai/airi · error · Error

OIDC flow status has expired or is no longer valid.

Error message

OIDC flow status has expired or is no longer valid.

What it means

completeOIDCSignIn throws this when the callback URL contains a code and state but consumeFlowState() returns nothing, meaning no PKCE/flow state was persisted (or it was consumed or expired) for this sign-in attempt. The OIDC Authorization Code + PKCE flow requires the locally stored state (code verifier, params) to match the callback; without it the code cannot be exchanged safely.

Source

Thrown at packages/stage-ui/src/libs/auth.ts:103

    callbackURL: authorizationUrl,
  })
}

/**
 * Completes an OIDC sign-in from a platform callback URL.
 *
 * The function returns false when the URL is not an OIDC callback.
 */
export async function completeOIDCSignIn(callbackUrl: string): Promise<boolean> {
  const url = new URL(callbackUrl)
  const code = url.searchParams.get('code')
  const state = url.searchParams.get('state')
  if (!code || !state)
    return false

  const persisted = consumeFlowState()
  if (!persisted)
    throw new Error('OIDC flow status has expired or is no longer valid.')

  const tokens = await exchangeCodeForTokens(code, persisted.flowState, persisted.params, state)
  await applyOIDCTokens(tokens, persisted.params.clientId)
  return true
}

/**
 * Initiate OIDC Authorization Code + PKCE sign-in flow.
 * Builds the authorization URL, persists PKCE state, and navigates.
 */
export async function signInOIDC(params: OIDCFlowParams) {
  const handler = authorizationHandler
  if (!handler)
    throw new Error('No authorization handler is registered for this app runtime.')

  const { provider, ...oidcParams } = params
  const { url, flowState } = await buildAuthorizationURL(oidcParams)
  persistFlowState(flowState, params)

View on GitHub (pinned to 0616eabd5b)

Solutions

  1. Restart the flow: call signInOIDC again to build a fresh authorization URL and persisted state, then complete sign-in from that attempt.
  2. Ensure completeOIDCSignIn runs in the same window/session that called signInOIDC (check storage access in iframes/private mode/Electron partitions).
  3. Make sure the redirect is handled exactly once — remove duplicate route handlers or listeners that consume the flow state first.
  4. Check that storage is enabled and the app origin is stable between initiation and callback.

Example fix

// before
const ok = await completeOIDCSignIn(window.location)
if (!ok) return

// after
try {
  const ok = await completeOIDCSignIn(window.location)
  if (!ok) return
} catch (err) {
  // stale callback (reload/bookmark): restart the flow
  await signInOIDC(params)
}
Defensive patterns

Strategy: fallback

Validate before calling

const hasFlowState = readFlowState() != null // or expose a probe from the auth lib
if (!hasFlowState) {
  // stale/duplicate callback: restart instead of completing
  await signInOIDC(params)
} else {
  await completeOIDCSignIn(url)
}

Type guard

null

Try / catch

try {
  await completeOIDCSignIn(window.location)
} catch (err) {
  if (err instanceof Error && err.message.includes('expired or is no longer valid')) {
    await signInOIDC(params) // restart the flow once; surface UI if it repeats
  } else throw err
}

Prevention

When it happens

Trigger: signInOIDC was never called (or ran in another window/session) before the redirect callback hit completeOIDCSignIn; the flow state was already consumed by a duplicate callback handling; storage was cleared or is unavailable (localStorage disabled, private mode, different origin/window); the user bookmarked or reloaded the callback URL after the state was consumed.

Common situations: Deep-linking the callback URL in an Electron app where the state was persisted in a different window; reloading or re-opening the OIDC redirect URI; browser storage partitioning or third-party storage blocking; two components both handling the OAuth redirect and the first consuming the state.

Related errors


AI-assisted analysis of moeru-ai/airi@0616eabd5b (2026-08-28). Data as JSON: /api/errors/b33a13c2b0630859. Report an issue: GitHub.