mastra-ai/mastra · error · Error
Failed to start OAuth callback server: ports ${firstPort}-${
Error message
Failed to start OAuth callback server: ports ${firstPort}-${lastPort} are all in use What it means
Error thrown by createOAuthCallbackServer after attempting to bind each candidate loopback port; if none could be bound, it reports the full candidate port range as in use. The OAuth flow needs a local HTTP server to receive the authorization redirect, so this is fatal to the flow.
Source
Thrown at packages/mcp/src/client/oauth-callback-server.ts:299
const hostname = candidates[0]!.hostname.replace(/^\[|\]$/g, '');
let boundUrl: URL | undefined;
let boundPort: number | undefined;
for (const candidate of candidates) {
const candidatePort = Number(candidate.port);
const error = await listen(server, candidatePort, hostname);
if (!error) {
boundUrl = candidate;
// Preserve the port we actually listened on. Reading URL.port back would
// return '' for the default 80/443, losing the effective port.
boundPort = candidatePort;
break;
}
}
if (!boundUrl || boundPort === undefined) {
const firstPort = Number(candidates[0]!.port);
const lastPort = Number(candidates[candidates.length - 1]!.port);
throw new Error(`Failed to start OAuth callback server: ports ${firstPort}-${lastPort} are all in use`);
}
// The bind-time 'error' listener is removed once 'listening' fires, so after
// this point the server has no 'error' handler. An emitted 'error' (e.g. a
// post-bind socket failure) with no listener throws and would crash the host
// process. Keep a persistent listener that settles the flow with the error
// instead of letting it become an uncaught exception.
server.on('error', error => {
settle({ error: error instanceof Error ? error : new Error(String(error)) });
});
return {
url: boundUrl,
port: boundPort,
waitForCode({ timeoutMs = DEFAULT_CALLBACK_TIMEOUT_MS } = {}) {
let timer: NodeJS.Timeout | undefined;
const timeout = new Promise<never>((_, reject) => {View on GitHub (pinned to 75dd419e61)
Solutions
- Identify and stop the processes occupying the ports (lsof -i :PORT / netstat), or wait for them to exit.
- Configure a different or wider callback port range in the provider/redirect URL.
- Ensure previous OAuth flows are cancelled/completed so their callback servers are released.
- In CI/containers, reserve unique port ranges per test or worker to avoid cross-process collisions.
Example fix
// before
new MCPOAuthClientProvider({ redirectUrl: 'http://localhost:3456/callback' }) // 3456 busy
// after
const freePort = await getFreePort(4000, 4100);
new MCPOAuthClientProvider({ redirectUrl: `http://localhost:${freePort}/callback` }) Defensive patterns
Strategy: retry
Validate before calling
import net from 'node:net';
function isPortFree(port: number): Promise<boolean> {
return new Promise(res => {
const s = net.createServer();
s.once('error', () => res(false));
s.once('listening', () => s.close(() => res(true)));
s.listen(port, 'localhost');
});
} Try / catch
try {
await startOAuthFlow();
} catch (e) {
if (e instanceof Error && e.message.includes('are all in use')) {
await releaseStaleServers(); // cancel previous flows / kill stale listeners
await startOAuthFlow(); // retry once
} else throw e;
} Prevention
- Configure a wide candidate port range for the callback server.
- Always cancel/complete prior OAuth flows so their callback servers are released.
- In CI, assign unique port ranges per worker.
- Check for stale processes holding the ports before starting flows.
When it happens
Trigger: Starting an OAuth authorization flow when every port in the configured candidate port range is already bound by other processes (or blocked by firewall/permissions).
Common situations: Stale dev servers or previous crashed runs still holding the callback port; multiple concurrent OAuth flows for different servers competing for the same range; containers/CI where the port range collides with other services.
Related errors
- Token exchange failed: ${error}
- Failed to fetch user info from Auth0
- Token exchange failed: ${error}
- Failed to fetch user info from Clerk
- Google token exchange failed: ${error}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/fbc1684ff125b563.
Report an issue: GitHub.