coleam00/Archon · error · OAuthCallbackPortBusyError

A previous '${provider}' login attempt is still holding the

Error message

A previous '${provider}' login attempt is still holding the OAuth callback port. Wait a few seconds and retry; if it persists, restart the Archon server.

What it means

OAuthCallbackPortBusyError thrown by startOAuth (oauth-bridge.ts:383). Before starting a new login the bridge cancels prior in-flight/superseded sessions; if the freshly started login fails immediately with a port-in-use error on the vendor's fixed callback port (anthropic binds 53692), the failure is classified via PORT_BUSY_RE and surfaced as this retryable error. It means a previous login attempt's callback server is still holding the port.

Source

Thrown at packages/core/src/credentials/oauth-bridge.ts:383

      session.firstSignal.resolve(true);
      getLog().warn(
        { err: sanitizeError(err as Error), userId, provider },
        'oauth_bridge.login_failed'
      );
    });

  // Wait for the first callback so the URL / user-code is available to return.
  await Promise.race([session.firstSignal.promise, sleep(START_FIRST_SIGNAL_MS)]);

  // An early login() failure → throw (route returns 500, CLI prints the message)
  // rather than returning a misleading { mode:'manual', url:undefined } (I1).
  if (session.status === 'error') {
    sessions.delete(sessionId);
    if (session.portBusy) {
      // Retryable: the cancel above releases the port as soon as the previous
      // login unwinds (microtasks for pi flows), so "retry shortly" is honest
      // advice — and a restart always clears it (#1963).
      throw new OAuthCallbackPortBusyError(
        `A previous '${provider}' login attempt is still holding the OAuth callback port. ` +
          'Wait a few seconds and retry; if it persists, restart the Archon server.'
      );
    }
    throw new Error(session.detail ?? 'Subscription login failed to start.');
  }

  // Superseded (or cancelled) while still waiting for the first signal — the
  // session is already gone from the map, so a 200 here would hand back a
  // url-less session the first poll immediately reports as "not found".
  // Throw the honest answer instead (S4).
  if (!sessions.has(sessionId)) {
    throw new Error('Login attempt was superseded by a newer one. Retry to start a fresh login.');
  }

  return {
    sessionId,
    mode: externalMode(session),

View on GitHub (pinned to 0773b97458)

Solutions

  1. Wait a few seconds and retry — the superseded login releases the port as soon as it unwinds (microtasks for pi flows).
  2. If it persists, restart the Archon server, which always frees the bound port.
  3. Catch OAuthCallbackPortBusyError and implement bounded retry with backoff in your client.
  4. Avoid firing a new login while one for the same vendor is still in flight; cancel it first (cancelOAuth) and let it settle.

Example fix

// before
await startOAuth(userId, 'anthropic');
// after
try {
  await startOAuth(userId, 'anthropic');
} catch (e) {
  if (e instanceof OAuthCallbackPortBusyError) {
    await sleep(3000);
    await startOAuth(userId, 'anthropic');
  } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check: ensure no login for a callback-server vendor is already in flight
if (hasActiveOAuthSession(userId, provider)) {
  await cancelOAuth(userId, provider); // or wait for it to settle
}

Type guard

function isOAuthCallbackPortBusyError(e: unknown): e is OAuthCallbackPortBusyError {
  return e instanceof OAuthCallbackPortBusyError;
}

Try / catch

try {
  await startOAuth(userId, 'anthropic');
} catch (e) {
  if (e instanceof OAuthCallbackPortBusyError) {
    await sleep(3000);
    await startOAuth(userId, 'anthropic'); // bounded retry; restart server if it persists
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling startOAuth for a callback-server vendor (anthropic) while a previous login attempt's local callback server still holds the fixed port — the new pi login() rejects with EADDRINUSE, session.portBusy is set, and startOAuth throws this error instead of an opaque failure (#1963).

Common situations: An abandoned browser login from minutes ago still unwinding; a crashed/hung previous login that never released port 53692; rapid restart attempts of the same login; long-lived server process that accumulated a leaked callback server.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/1b28894a2442d11c. Report an issue: GitHub.