{"record":{"id":"99d9b8a97a491fa0","repo":"odysseus-dev/odysseus","slug":"request-failed-http-response-status","errorCode":null,"errorMessage":"Request failed (HTTP ${response.status})","messagePattern":"Request failed \\(HTTP (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/providerDeviceFlow.js","lineNumber":60,"sourceCode":"  if (payload && typeof payload.message === 'string' && payload.message.trim()) {\n    return payload.message.trim();\n  }\n  return fallback;\n}\n\nexport function formatDeviceFlowError(error, fallback = 'Request failed') {\n  if (!error) return fallback;\n  if (typeof error === 'string') return error;\n  if (error.detail) return String(error.detail);\n  if (error.message) return String(error.message);\n  return fallback;\n}\n\nasync function _fetchJson(fetchImpl, url, options, fallback) {\n  const response = await fetchImpl(url, options);\n  const payload = await _jsonOrEmpty(response);\n  if (!response.ok) {\n    throw new Error(_messageFromPayload(payload, fallback || `Request failed (HTTP ${response.status})`));\n  }\n  return payload;\n}\n\nfunction _defaultSleep(ms) {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nasync function _callCallback(fn, payload) {\n  if (typeof fn === 'function') await fn(payload);\n}\n\nexport async function runProviderDeviceFlow(provider, options = {}) {\n  const cfg = PROVIDER_DEVICE_FLOWS[provider];\n  if (!cfg) throw new Error(`Unknown device-flow provider: ${provider}`);\n\n  const fetchImpl = options.fetchImpl || globalThis.fetch?.bind(globalThis);\n  if (!fetchImpl) throw new Error('Fetch API is unavailable');","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/providerDeviceFlow.js#L42-L78","documentation":"Default message produced by _fetchJson in providerDeviceFlow.js when a device-flow HTTP call (start or poll) returns non-ok AND the response body carried no usable message. _messageFromPayload prefers detail then message from the parsed JSON; only when the body is empty/unparseable does the template literal with the status code become the message.","triggerScenarios":"POST to the provider's startUrl (e.g. /api/auth/{provider}/device/start) returning 500 with an empty body; poll endpoint returning 502 from a proxy with an HTML page that fails JSON parsing; CSRF/auth cookie missing so the server rejects with a bare 403.","commonSituations":"Reverse proxy (nginx) intercepting the API route and returning plain error pages; app server crashed and the static file server returned a 404 page; running the module in a test environment without a fetch stub (though missing fetch throws error 92 instead).","solutions":["Open devtools network tab and read the actual response body/status for the failed start or poll call.","If a proxy is intercepting, fix the routing so /api/ reaches the app server.","If 5xx with empty body, check app server logs for the crash behind it.","Ensure credentials:'same-origin' cookie is present (log in to the app first)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (typeof fetchImpl !== 'function') throw new Error('Fetch API is unavailable');","typeGuard":"const hasPayloadMessage = (p) => !!p && (typeof p.detail === 'string' || typeof p.message === 'string');","tryCatchPattern":"try { return await _fetchJson(fetchImpl, url, options, fallback); } catch (e) { return formatDeviceFlowError(e, `Request failed for ${url}`); }","preventionTips":["Always send JSON error bodies from the API so detail/message extraction works","Keep the fallback string provider-specific for better UX","Test device-flow endpoints through the same proxy path as production"],"tags":["oauth","device-flow","http","fetch"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}