{"record":{"id":"ddd20691cc277ea8","repo":"odysseus-dev/odysseus","slug":"unknown-device-flow-provider-provider","errorCode":null,"errorMessage":"Unknown device-flow provider: ${provider}","messagePattern":"Unknown device-flow provider: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/providerDeviceFlow.js","lineNumber":75,"sourceCode":"  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');\n\n  const openWindow = options.openWindow || ((url) => {\n    if (globalThis.window && typeof globalThis.window.open === 'function') {\n      globalThis.window.open(url, '_blank', 'noopener');\n    }\n  });\n  const sleep = options.sleep || _defaultSleep;\n  const now = options.now || (() => Date.now());\n  const formData = options.formData || _formData();\n\n  const start = await _fetchJson(fetchImpl, cfg.startUrl, {\n    method: 'POST',\n    body: formData,\n    credentials: 'same-origin',\n  }, `Failed to start ${cfg.label} sign-in`);","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/providerDeviceFlow.js#L57-L93","documentation":"Thrown at the top of runProviderDeviceFlow when the provider string is not a key in PROVIDER_DEVICE_FLOWS. It fails before any network activity — pure config/validation failure. The provider comes from whatever caller passed (typically a button's data attribute or settings).","triggerScenarios":"Calling runProviderDeviceFlow('github') when the registry only has entries like 'google'/'anthropic'; a typo or case mismatch ('Google'); a provider removed in a newer build while old UI markup still references it.","commonSituations":"Frontend/backend version skew after adding a new provider to the UI but not to the flow registry; stale cached JS bundle referencing an old provider id; hand-written integrations copying the wrong provider slug.","solutions":["Log/console the provider string being passed and compare with the keys exported in PROVIDER_DEVICE_FLOWS.","Use the exact slug from the registry (case-sensitive).","If UI references a provider the registry lacks, update the bundle or add the provider config.","Guard with Object.hasKey check before invoking (see typeGuard)."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"import { PROVIDER_DEVICE_FLOWS } from './providerDeviceFlow.js';\nif (!Object.prototype.hasOwnProperty.call(PROVIDER_DEVICE_FLOWS, provider)) { showError(`Unsupported provider: ${provider}`); return; }","typeGuard":"const isKnownProvider = (p) => typeof p === 'string' && Object.prototype.hasOwnProperty.call(PROVIDER_DEVICE_FLOWS, p);","tryCatchPattern":"try { await runProviderDeviceFlow(provider); } catch (e) { if (/Unknown device-flow provider/.test(e.message)) showError('This sign-in provider is not available in this build'); else throw e; }","preventionTips":["Derive provider buttons from PROVIDER_DEVICE_FLOWS keys so UI and registry cannot diverge","Validate the provider slug before starting the flow","Case-normalize provider strings at the call site"],"tags":["oauth","device-flow","validation","config"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}