{"record":{"id":"910d4cb88914bd5e","repo":"decolua/9router","slug":"startdata-reason-startdata-error-failed-to","errorCode":null,"errorMessage":"startData.reason || startData.error || `Failed to start ${providerId} callback server`","messagePattern":"startData\\.reason \\|\\| startData\\.error \\|\\| `Failed to start (.+?) callback server`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/shared/components/OAuthModal.js","lineNumber":188,"sourceCode":"        setError(err.message);\n        setStep(\"error\");\n        setPolling(false);\n        return;\n      }\n    }\n\n    setError(\"Authorization timeout\");\n    setStep(\"error\");\n    setPolling(false);\n  }, [provider, onSuccess]);\n\n  // Trae/Windsurf proxy OAuth flow: dynamic-port local callback → auto exchange.\n  const startProxyFlow = useCallback(async (providerId) => {\n    // 1. Start the local callback server (returns a dynamic port + callback URL).\n    const startRes = await fetch(`/api/oauth/${providerId}/start-proxy`);\n    const startData = await startRes.json();\n    if (!startRes.ok || !startData.success || !startData.callbackUrl) {\n      throw new Error(startData.reason || startData.error || `Failed to start ${providerId} callback server`);\n    }\n    // 2. Build the authorize URL with redirect_uri = proxy callback URL.\n    const authorizeUrl = new URL(`/api/oauth/${providerId}/authorize`, window.location.origin);\n    authorizeUrl.searchParams.set(\"redirect_uri\", startData.callbackUrl);\n    const authRes = await fetch(authorizeUrl);\n    const authData = await authRes.json();\n    if (!authRes.ok) throw new Error(authData.error);\n    // 3. Register the session so the proxy can match the incoming callback.\n    //    Zed also passes code_verifier (encodes the RSA private key for decrypt);\n    //    sent via POST body so the private key never lands in URL/query logs.\n    const regBody = { state: authData.state };\n    if (authData.codeVerifier) regBody.codeVerifier = authData.codeVerifier;\n    await fetch(`/api/oauth/${providerId}/register-session`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify(regBody),\n    });\n    // 4. Open popup; proxy auto-exchanges on callback, modal polls poll-status.","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/shared/components/OAuthModal.js#L170-L206","documentation":"startProxyFlow (used by Trae/Windsurf-style providers) first calls GET /api/oauth/<providerId>/start-proxy to spin up a local dynamic-port callback server. If the request fails, reports success:false, or omits callbackUrl, the modal throws with the server's `reason` or `error`, else the generic `Failed to start <providerId> callback server` message. The callback server is required because these providers redirect to a localhost port that must exist before the authorize URL is opened.","triggerScenarios":"The start-proxy route fails to bind a local port (port exhaustion, no loopback permission), the provider isn't configured for proxy OAuth, the route returns an error body without success/callbackUrl, or the dashboard server itself is unreachable (request throws before JSON parsing — in that case the generic fetch error, not this message, surfaces).","commonSituations":"Running in an environment where binding localhost ports is blocked (containers, some CI sandboxes); another process squats the port range; dashboard started without the OAuth proxy feature enabled; providerId typo'd so the dynamic route 404s.","solutions":["Check the response body's `reason`/`error` field (Network tab) — it names the concrete start-up failure.","Verify the environment allows binding a localhost port for the callback server (containers/sandboxes often don't).","Confirm the providerId matches a proxy-capable provider and that /api/oauth/<providerId>/start-proxy exists (a 404 means wrong id or stale build).","Restart the dashboard server; a wedged previous callback server can prevent a new one from starting."],"exampleFix":"// before\nif (!startRes.ok || !startData.success || !startData.callbackUrl) {\n  throw new Error(startData.reason || startData.error || `Failed to start ${providerId} callback server`);\n}\n// after\nif (!startRes.ok || !startData?.success || !startData?.callbackUrl) {\n  throw new Error(startData?.reason || startData?.error || `Failed to start ${providerId} callback server (HTTP ${startRes.status})`);\n}","handlingStrategy":"fallback","validationCode":"// probe the endpoint before driving the flow\nconst startRes = await fetch(`/api/oauth/${providerId}/start-proxy`);\nif (!startRes.ok) {\n  console.warn(`start-proxy unavailable for ${providerId} (HTTP ${startRes.status}) — falling back to manual code entry`);\n  openManualCodeStep(providerId);\n  return;\n}","typeGuard":"function isStartProxyOk(data) {\n  return typeof data === \"object\" && data !== null\n    && data.success === true\n    && typeof data.callbackUrl === \"string\" && data.callbackUrl.length > 0;\n}","tryCatchPattern":"try {\n  const startRes = await fetch(`/api/oauth/${providerId}/start-proxy`);\n  const startData = await startRes.json().catch(() => ({}));\n  if (!isStartProxyOk(startData)) {\n    throw new Error(startData.reason || startData.error || `Failed to start ${providerId} callback server (HTTP ${startRes.status})`);\n  }\n} catch (err) {\n  setError(`${err.message}. If localhost callbacks are blocked (container/sandbox), use manual code entry instead.`);\n}","preventionTips":["Ensure the runtime environment permits binding localhost ports.","Fall back to manual-code entry when the proxy callback server can't start.","Use exact provider slugs so the dynamic route resolves.","Restart the dashboard if a stale callback server is holding ports."],"tags":["oauth","callback-server","proxy","localhost"],"backgroundTag":"local-callback-server-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}