{"record":{"id":"1b28894a2442d11c","repo":"coleam00/Archon","slug":"a-previous-provider-login-attempt-is-still-ho","errorCode":null,"errorMessage":"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.","messagePattern":"A previous '(.+?)' login attempt is still holding the OAuth callback port\\. Wait a few seconds and retry; if it persists, restart the Archon server\\.","errorType":"exception","errorClass":"OAuthCallbackPortBusyError","httpStatus":null,"severity":"error","filePath":"packages/core/src/credentials/oauth-bridge.ts","lineNumber":383,"sourceCode":"      session.firstSignal.resolve(true);\n      getLog().warn(\n        { err: sanitizeError(err as Error), userId, provider },\n        'oauth_bridge.login_failed'\n      );\n    });\n\n  // Wait for the first callback so the URL / user-code is available to return.\n  await Promise.race([session.firstSignal.promise, sleep(START_FIRST_SIGNAL_MS)]);\n\n  // An early login() failure → throw (route returns 500, CLI prints the message)\n  // rather than returning a misleading { mode:'manual', url:undefined } (I1).\n  if (session.status === 'error') {\n    sessions.delete(sessionId);\n    if (session.portBusy) {\n      // Retryable: the cancel above releases the port as soon as the previous\n      // login unwinds (microtasks for pi flows), so \"retry shortly\" is honest\n      // advice — and a restart always clears it (#1963).\n      throw new OAuthCallbackPortBusyError(\n        `A previous '${provider}' login attempt is still holding the OAuth callback port. ` +\n          'Wait a few seconds and retry; if it persists, restart the Archon server.'\n      );\n    }\n    throw new Error(session.detail ?? 'Subscription login failed to start.');\n  }\n\n  // Superseded (or cancelled) while still waiting for the first signal — the\n  // session is already gone from the map, so a 200 here would hand back a\n  // url-less session the first poll immediately reports as \"not found\".\n  // Throw the honest answer instead (S4).\n  if (!sessions.has(sessionId)) {\n    throw new Error('Login attempt was superseded by a newer one. Retry to start a fresh login.');\n  }\n\n  return {\n    sessionId,\n    mode: externalMode(session),","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/credentials/oauth-bridge.ts#L365-L401","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Wait a few seconds and retry — the superseded login releases the port as soon as it unwinds (microtasks for pi flows).","If it persists, restart the Archon server, which always frees the bound port.","Catch OAuthCallbackPortBusyError and implement bounded retry with backoff in your client.","Avoid firing a new login while one for the same vendor is still in flight; cancel it first (cancelOAuth) and let it settle."],"exampleFix":"// before\nawait startOAuth(userId, 'anthropic');\n// after\ntry {\n  await startOAuth(userId, 'anthropic');\n} catch (e) {\n  if (e instanceof OAuthCallbackPortBusyError) {\n    await sleep(3000);\n    await startOAuth(userId, 'anthropic');\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"// Pre-check: ensure no login for a callback-server vendor is already in flight\nif (hasActiveOAuthSession(userId, provider)) {\n  await cancelOAuth(userId, provider); // or wait for it to settle\n}","typeGuard":"function isOAuthCallbackPortBusyError(e: unknown): e is OAuthCallbackPortBusyError {\n  return e instanceof OAuthCallbackPortBusyError;\n}","tryCatchPattern":"try {\n  await startOAuth(userId, 'anthropic');\n} catch (e) {\n  if (e instanceof OAuthCallbackPortBusyError) {\n    await sleep(3000);\n    await startOAuth(userId, 'anthropic'); // bounded retry; restart server if it persists\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never start a second login for the same vendor while one is in flight.","Cancel in-flight logins explicitly and await settlement before restarting.","Restart the Archon server after crashes that may have leaked the callback port.","Surface this error class distinctly in UI so users see it as retryable."],"tags":["oauth","port-in-use","eaddrinuse","retryable"],"backgroundTag":"address-already-in-use","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}