nocobase/nocobase · error

OAuth device sign-in expired. Run `nb env auth ${options.env

Error message

OAuth device sign-in expired. Run `nb env auth ${options.envName}` to try again.

What it means

Thrown by pollDeviceToken when the token endpoint returns the OAuth error code `expired_token`, meaning the device_code issued at the start of device authorization has passed its server-defined lifetime before the user completed sign-in. The CLI stops polling immediately and tells the user to restart the flow, because the device code is no longer valid.

Source

Thrown at packages/core/cli/src/lib/env-auth.ts:1106

    if (response.ok) {
      if (!data || typeof data !== 'object' || typeof data.access_token !== 'string') {
        throw new Error('OAuth token response is missing access_token.');
      }
      return data as OauthTokenResponse;
    }

    const oauthError = typeof data === 'object' && data ? String((data as { error?: unknown }).error || '') : '';
    if (oauthError === 'authorization_pending') {
      updateTask(`Waiting for you to approve device sign-in for "${options.envName}"...`);
      continue;
    }
    if (oauthError === 'slow_down') {
      intervalMs += 5_000;
      updateTask(`OAuth server asked us to slow down. Polling again in ${Math.ceil(intervalMs / 1000)}s...`);
      continue;
    }
    if (oauthError === 'expired_token') {
      throw new Error(`OAuth device sign-in expired. Run \`nb env auth ${options.envName}\` to try again.`);
    }
    if (oauthError === 'access_denied') {
      throw new Error('OAuth device sign-in was denied.');
    }

    throw new Error(formatOauthError('Failed to poll OAuth device authorization', data, response.status));
  }

  throw new Error(`OAuth device sign-in timed out. Run \`nb env auth ${options.envName}\` to try again.`);
}

async function refreshOauthAccessToken(options: {
  envName: string;
  baseUrl: string;
  auth: OauthAuthConfig;
  scope?: AuthStoreOptions['scope'];
}) {
  if (!options.auth.refreshToken || !options.auth.clientId) {

View on GitHub (pinned to fa42722fef)

Solutions

  1. Run `nb env auth <envName>` again and complete the browser approval promptly.
  2. Open the verification_uri as soon as it is printed and approve before the code expires.
  3. If the server expires codes too quickly for your workflow, ask the admin to increase the device-code lifetime.
Defensive patterns

Strategy: fallback

Try / catch

try {
  await cliEnvAuth(envName);
} catch (err) {
  if (String(err.message).includes('device sign-in expired')) {
    // restart the flow and approve promptly this time
    await cliEnvAuth(envName);
  } else throw err;
}

Prevention

When it happens

Trigger: POST to the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:device_code returns {"error":"expired_token"} — i.e. the user did not approve the sign-in in the browser before the device code expired (typically 5-15 minutes).

Common situations: Developer opened the verification URL late or left the browser tab pending; slow_down backoff stretched polling past expiry; the CLI was left running while the user was away; a server configured with a very short device-code TTL.

Related errors


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/f64047c533502c00. Report an issue: GitHub.