google-gemini/gemini-cli · error · FatalAuthenticationError

Failed to open browser: ${getErrorMessage(err)}

Error message

Failed to open browser: ${getErrorMessage(err)}

What it means

Thrown as a FatalAuthenticationError when the open() library fails to launch a browser to display the OAuth authentication URL. The code calls open(webLogin.authUrl) to spawn the system's default browser (xdg-open on Linux, open on macOS, start on Windows). If the spawn fails — typically because the command doesn't exist — the error is caught, a user-feedback event is emitted, and a fatal error is thrown. The error message suggests NO_BROWSER=true as a workaround.

Source

Thrown at packages/core/src/code_assist/oauth2.ts:356

      // Without this, if `open` fails to spawn a process (e.g., `xdg-open` is not found
      // in a minimal Docker container), it will emit an unhandled 'error' event,
      // causing the entire Node.js process to crash.
      childProcess.on('error', (error) => {
        coreEvents.emit(CoreEvent.UserFeedback, {
          severity: 'error',
          message:
            `Failed to open browser with error: ${getErrorMessage(error)}\n` +
            `Please try running again with NO_BROWSER=true set.`,
        });
      });
    } catch (err) {
      coreEvents.emit(CoreEvent.UserFeedback, {
        severity: 'error',
        message:
          `Failed to open browser with error: ${getErrorMessage(err)}\n` +
          `Please try running again with NO_BROWSER=true set.`,
      });
      throw new FatalAuthenticationError(
        `Failed to open browser: ${getErrorMessage(err)}`,
      );
    }
    coreEvents.emit(CoreEvent.UserFeedback, {
      severity: 'info',
      message: 'Waiting for authentication...\n',
    });

    // Add timeout to prevent infinite waiting when browser tab gets stuck
    const authTimeout = 5 * 60 * 1000; // 5 minutes timeout
    let timeoutId: NodeJS.Timeout | undefined;
    const timeoutPromise = new Promise<never>((_, reject) => {
      timeoutId = setTimeout(() => {
        reject(
          new FatalAuthenticationError(
            'Authentication timed out after 5 minutes. The browser tab may have gotten stuck in a loading state. ' +
              'Please try again or use NO_BROWSER=true for manual authentication.',
          ),

View on GitHub (pinned to 5024443c72)

Solutions

  1. Set NO_BROWSER=true and run in an interactive terminal to use the manual user-code flow instead.
  2. Install xdg-utils (provides xdg-open) on Linux: apt-get install xdg-utils.
  3. In containers, prefer GEMINI_API_KEY or ADC over browser-based OAuth.
  4. If a browser is available but not detected, set the BROWSER environment variable to the browser binary path.
Defensive patterns

Strategy: validation

Validate before calling

// Check for a browser handler before attempting to open
import { existsSync } from 'node:fs';

function hasBrowserHandler(): boolean {
  if (process.platform === 'linux') {
    return existsSync('/usr/bin/xdg-open');
  }
  return true; // macOS/Windows have built-in handlers
}

if (!hasBrowserHandler()) {
  process.env['NO_BROWSER'] = 'true';
  console.warn('No browser handler found. Using manual auth flow.');
}

Try / catch

try {
  client = await getOauthClient(authType, config);
} catch (e) {
  if (e instanceof FatalAuthenticationError && e.message.startsWith('Failed to open browser')) {
    // Fall back to manual code entry
    process.env['NO_BROWSER'] = 'true';
    client = await getOauthClient(authType, config);
  } else throw e;
}

Prevention

When it happens

Trigger: In the interactive OAuth branch, open(webLogin.authUrl) throws because the OS has no default browser handler. The open library spawns a child process (e.g., xdg-open); if the binary is missing or errors, the promise rejects.

Common situations: Minimal Docker container or server without a browser or xdg-open installed; DISPLAY environment variable unset on Linux; a headless environment where no GUI browser exists; the default browser binary path is misconfigured; SELinux/AppArmor blocking process spawn.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/9cfe5a54afe541c9. Report an issue: GitHub.