{"record":{"id":"e5397202e9b5a046","repo":"can1357/oh-my-pi","slug":"https-loopback-redirect-uris-cannot-reuse-the-same","errorCode":null,"errorMessage":"HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth.callbackPort","messagePattern":"HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth\\.callbackPort","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/oauth-flow.ts","lineNumber":170,"sourceCode":"function getUriPort(uri: URL): number {\n\tif (uri.port !== \"\") return Number(uri.port);\n\treturn uri.protocol === \"https:\" ? 443 : 80;\n}\n\nfunction validateRedirectConfig(config: MCPOAuthConfig, redirectUri: string | undefined): void {\n\tconst parsed = parseRedirectUri(redirectUri);\n\tif (parsed?.protocol !== \"https:\" || !isLoopbackHostname(parsed.hostname)) {\n\t\treturn;\n\t}\n\n\tif (config.callbackPort === undefined) {\n\t\tthrow new Error(\n\t\t\t\"HTTPS loopback redirect URIs require oauth.callbackPort to point at the local HTTP callback listener behind your TLS terminator\",\n\t\t);\n\t}\n\n\tif (config.callbackPort === getUriPort(parsed)) {\n\t\tthrow new Error(\n\t\t\t\"HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth.callbackPort\",\n\t\t);\n\t}\n}\n\nfunction resolveCallbackPort(callbackPort: number | undefined, redirectUri: string | undefined): number {\n\tif (callbackPort !== undefined) return callbackPort;\n\n\tconst parsed = parseRedirectUri(redirectUri);\n\tif (parsed?.protocol !== \"http:\" || !isLoopbackHostname(parsed.hostname)) {\n\t\treturn DEFAULT_PORT;\n\t}\n\n\tconst port = getUriPort(parsed);\n\treturn Number.isFinite(port) && port > 0 ? port : DEFAULT_PORT;\n}\n\nfunction resolveCallbackPath(callbackPath: string | undefined, redirectUri: string | undefined): string {","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/oauth-flow.ts#L152-L188","documentation":"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.","triggerScenarios":"Setting oauth.redirectUri to https://localhost:<port>/... and oauth.callbackPort to the SAME <port> (e.g. both 8443) when resolving callback options via resolveCallbackOptions.","commonSituations":"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.","solutions":["Choose a different port for oauth.callbackPort and configure the TLS terminator to forward the redirect port to it","If you don't actually need TLS, change the redirect URI to http://localhost:<port>/... so no terminator/port split is required","Pick an unused high port for the local listener to also avoid 'address in use' conflicts"],"exampleFix":"// before: same port on both sides\n\"oauth\": { \"redirectUri\": \"https://localhost:8443/auth/callback\", \"callbackPort\": 8443 }\n// after: terminator on 8443 forwards to listener on 1455\n\"oauth\": { \"redirectUri\": \"https://localhost:8443/auth/callback\", \"callbackPort\": 1455 }","handlingStrategy":"validation","validationCode":"const p = new URL(config.oauth.redirectUri);\nif (p.protocol === 'https:' && config.oauth.callbackPort === p.port) {\n  throw new Error('oauth.callbackPort must differ from the https redirect port');\n}","typeGuard":null,"tryCatchPattern":"try {\n  startOAuthFlow(config);\n} catch (e) {\n  if (e.message.includes('cannot reuse the same local port')) {\n    throw new Error('Point oauth.callbackPort at a separate port the TLS terminator forwards to, not the redirect port');\n  } else throw e;\n}","preventionTips":["Treat the https redirect port and callbackPort as two different services by design","Pick callbackPort from an unused port range to avoid both collisions and 'address in use' errors","Configure terminator forwarding (https:PORT -> http:callbackPort) and verify with curl before running the flow","Never copy the redirect port into callbackPort"],"tags":["oauth","tls","configuration","port-conflict"],"backgroundTag":"oauth-loopback-redirect-tls","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}