{"record":{"id":"91cd8484915d27fc","repo":"decolua/9router","slug":"windsurf-path-invalid-json","errorCode":null,"errorMessage":"`Windsurf ${path} invalid JSON`","messagePattern":"`Windsurf (.+?) invalid JSON`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/providers/windsurf.js","lineNumber":21,"sourceCode":"\n// ───────────────────────────────────────────────────────────────────────────\n// Windsurf OAuth helpers\n// ───────────────────────────────────────────────────────────────────────────\n\nasync function windsurfSeatRequest(baseUrl, path, body) {\n  const url = `${baseUrl.replace(/\\/$/, \"\")}${path}`;\n  const res = await fetch(url, {\n    method: \"POST\",\n    headers: {\n      Accept: \"application/json\",\n      \"Content-Type\": \"application/json\",\n      \"User-Agent\": WINDSURF_CONFIG.userAgent,\n    },\n    body: JSON.stringify(body),\n  });\n  const text = await res.text();\n  if (!res.ok) throw new Error(`Windsurf ${path} HTTP ${res.status}: ${text.slice(0, 200)}`);\n  try { return JSON.parse(text); } catch { throw new Error(`Windsurf ${path} invalid JSON`); }\n}\n\n// Parse Windsurf callback (query string or full URL): ?access_token=...&state=...\nfunction parseWindsurfCallback(raw, expectedState) {\n  const text = String(raw || \"\").trim();\n  let queryStr = text;\n  if (text.includes(\"?\")) queryStr = text.slice(text.indexOf(\"?\") + 1);\n  if (text.startsWith(\"#\")) queryStr = text.slice(1);\n  const params = Object.fromEntries(new URLSearchParams(queryStr));\n  const pick = (keys) => {\n    for (const k of keys) { const v = params[k]; if (v && String(v).trim()) return String(v).trim(); }\n    return null;\n  };\n  const err = pick([\"error\"]);\n  if (err) {\n    const desc = pick([\"error_description\"]);\n    throw new Error(desc ? `Windsurf auth failed: ${err} (${desc})` : `Windsurf auth failed: ${err}`);\n  }","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providers/windsurf.js#L3-L39","documentation":"After a 2xx response, windsurfSeatRequest parses the body with JSON.parse and throws this if the body is not valid JSON. It exists because Windsurf endpoints can return HTML login/error pages or empty bodies with a 200 status. The message includes the request path so you know which endpoint misbehaved.","triggerScenarios":"A Windsurf endpoint (RegisterUser, GetOneTimeAuthToken, GetCurrentUser) returns 200 with an HTML page, empty string, or otherwise non-JSON body — typically behind a captive portal/SSO redirect, Cloudflare challenge, or after an endpoint path changed.","commonSituations":"Corporate proxies rewriting responses; hitting a Cloudflare challenge page; API host migrated so the old path serves HTML; truncated response on flaky networks.","solutions":["Log/inspect the raw response body (temporarily wrap the fetch) to see what is actually returned","Confirm WINDSURF_CONFIG endpoint paths and base URLs are current (paths changed upstream)","Bypass proxies/VPN or disable TLS-intercepting middleboxes during OAuth","Retry — transient gateway issues can yield truncated bodies","Update the library / Windsurf provider config to the latest endpoint definitions"],"exampleFix":"// before\nconst data = await windsurfSeatRequest(apiServerUrl, WINDSURF_CONFIG.registerPath, body);\n// after: distinguish upstream HTML from real protocol errors\nlet data;\ntry { data = await windsurfSeatRequest(apiServerUrl, WINDSURF_CONFIG.registerPath, body); }\ncatch (e) {\n  if (e.message.includes('invalid JSON')) throw new Error('Windsurf endpoint returned non-JSON (proxy/SSO redirect?) — check network');\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// check whether the Windsurf host is being intercepted before the flow\nconst head = await fetch(baseUrl, { method: 'HEAD' });\nconst ct = head.headers.get('content-type') || '';\nif (ct.includes('text/html')) console.warn('Proxy/portal intercepting Windsurf traffic — expect invalid JSON');","typeGuard":"const isInvalidJsonError = (e) => e instanceof Error && e.message.includes('invalid JSON');","tryCatchPattern":"try { data = await windsurfSeatRequest(url, path, body); }\ncatch (e) {\n  if (isInvalidJsonError(e)) throw new Error('Windsurf returned non-JSON — check proxy/VPN or endpoint path: ' + e.message);\n  throw e;\n}","preventionTips":["Disable TLS-intercepting proxies/VPNs during OAuth flows","Verify endpoint paths against the current Windsurf API after library updates","Log the raw response body when debugging to spot HTML pages","Retry once on transient truncation before failing the flow"],"tags":["oauth","json-parsing","windsurf","upstream-api"],"backgroundTag":"invalid-json-response","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}