can1357/oh-my-pi · error · Error

HTTPS loopback redirect URIs cannot reuse the same local por

Error message

HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth.callbackPort

What it means

When an HTTPS loopback redirect URI is configured, the OAuth flow uses two ports: the HTTPS port from the redirect URI (fronted by a TLS terminator) and the plain-HTTP callbackPort where the library's local listener actually binds. If callbackPort equals the port in the redirect URI, the listener would collide with the terminator on the same port, so validation rejects it.

Source

Thrown at packages/coding-agent/src/mcp/oauth-flow.ts:170

function getUriPort(uri: URL): number {
	if (uri.port !== "") return Number(uri.port);
	return uri.protocol === "https:" ? 443 : 80;
}

function validateRedirectConfig(config: MCPOAuthConfig, redirectUri: string | undefined): void {
	const parsed = parseRedirectUri(redirectUri);
	if (parsed?.protocol !== "https:" || !isLoopbackHostname(parsed.hostname)) {
		return;
	}

	if (config.callbackPort === undefined) {
		throw new Error(
			"HTTPS loopback redirect URIs require oauth.callbackPort to point at the local HTTP callback listener behind your TLS terminator",
		);
	}

	if (config.callbackPort === getUriPort(parsed)) {
		throw new Error(
			"HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth.callbackPort",
		);
	}
}

function resolveCallbackPort(callbackPort: number | undefined, redirectUri: string | undefined): number {
	if (callbackPort !== undefined) return callbackPort;

	const parsed = parseRedirectUri(redirectUri);
	if (parsed?.protocol !== "http:" || !isLoopbackHostname(parsed.hostname)) {
		return DEFAULT_PORT;
	}

	const port = getUriPort(parsed);
	return Number.isFinite(port) && port > 0 ? port : DEFAULT_PORT;
}

function resolveCallbackPath(callbackPath: string | undefined, redirectUri: string | undefined): string {

View on GitHub (pinned to 9690622007)

Solutions

  1. Choose a different port for oauth.callbackPort and configure the TLS terminator to forward the redirect port to it
  2. If you don't actually need TLS, change the redirect URI to http://localhost:<port>/... so no terminator/port split is required
  3. Pick an unused high port for the local listener to also avoid 'address in use' conflicts

Example fix

// before: same port on both sides
"oauth": { "redirectUri": "https://localhost:8443/auth/callback", "callbackPort": 8443 }
// after: terminator on 8443 forwards to listener on 1455
"oauth": { "redirectUri": "https://localhost:8443/auth/callback", "callbackPort": 1455 }
Defensive patterns

Strategy: validation

Validate before calling

const p = new URL(config.oauth.redirectUri);
if (p.protocol === 'https:' && config.oauth.callbackPort === p.port) {
  throw new Error('oauth.callbackPort must differ from the https redirect port');
}

Try / catch

try {
  startOAuthFlow(config);
} catch (e) {
  if (e.message.includes('cannot reuse the same local port')) {
    throw new Error('Point oauth.callbackPort at a separate port the TLS terminator forwards to, not the redirect port');
  } else throw e;
}

Prevention

When it happens

Trigger: Setting oauth.redirectUri to https://localhost:<port>/... and oauth.callbackPort to the SAME <port> (e.g. both 8443) when resolving callback options via resolveCallbackOptions.

Common situations: Users setting callbackPort to the same value as the redirect port because they assumed they must match; copy-pasting the redirect port into callbackPort; not understanding the terminator-forward-to-listener topology.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e5397202e9b5a046. Report an issue: GitHub.