{"record":{"id":"f6d9654a3e22333b","repo":"sipeed/picoclaw","slug":"invalid-device-code-response","errorCode":null,"errorMessage":"Invalid device code response","messagePattern":"Invalid device code response","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/frontend/src/hooks/use-credentials-page.ts","lineNumber":251,"sourceCode":"    },\n    [bumpActionToken, isActionTokenCurrent, t],\n  )\n\n  const startOpenAIDeviceCode = useCallback(async () => {\n    const actionToken = bumpActionToken()\n    setActiveAction(\"openai:device\")\n    setError(\"\")\n\n    try {\n      const resp = await loginOAuth({\n        provider: \"openai\",\n        method: \"device_code\",\n      })\n      if (!isActionTokenCurrent(actionToken)) {\n        return\n      }\n      if (!resp.flow_id || !resp.user_code || !resp.verify_url) {\n        throw new Error(t(\"credentials.errors.invalidDeviceResponse\"))\n      }\n\n      const flow: OAuthFlowState = {\n        flow_id: resp.flow_id,\n        provider: \"openai\",\n        method: \"device_code\",\n        status: \"pending\",\n        user_code: resp.user_code,\n        verify_url: resp.verify_url,\n        interval: resp.interval,\n        expires_at: resp.expires_at,\n      }\n\n      setDeviceFlow(flow)\n      setDeviceSheetOpen(true)\n      setActiveFlow(flow)\n      setWatchFlowID(resp.flow_id)\n      setWatchMode(\"poll\")","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/frontend/src/hooks/use-credentials-page.ts#L233-L269","documentation":"Thrown in startOpenAIDeviceCode (web/frontend/src/hooks/use-credentials-page.ts:251, message from i18n key credentials.errors.invalidDeviceResponse) after POST /api/oauth/login with {provider: \"openai\", method: \"device_code\"} returns 2xx but the body is missing flow_id, user_code, or verify_url. Like error 22 it is a defensive check of the backend device-code contract — the device sheet cannot be rendered without a user code and verification URL.","triggerScenarios":"Clicking 'OpenAI device code' login when the backend has no OpenAI OAuth client configured, the backend's device-flow endpoint returned an empty/partial object, or a middlebox altered the response. Triggered when resp.flow_id, resp.user_code, or resp.verify_url is falsy.","commonSituations":"OpenAI provider configured with API key only (no OAuth app) so device flow returns nothing; backend version that lacks device_code support; corporate proxy mangling the JSON; provider status endpoint says available but login endpoint disagrees.","solutions":["Configure the OpenAI OAuth client (client id/secret with device flow enabled) in the launcher backend config","Check the network tab: inspect POST /api/oauth/login response body to see which of flow_id/user_code/verify_url is missing","Update the launcher backend to a version whose device flow returns the full payload","Fall back to browser-method login or API-key credentials for OpenAI while device flow is unavailable"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isDeviceLoginResponse(\n  resp: unknown,\n): resp is { flow_id: string; user_code: string; verify_url: string; interval?: number; expires_at?: string } {\n  if (!resp || typeof resp !== \"object\") return false\n  const r = resp as Record<string, unknown>\n  return (\n    typeof r.flow_id === \"string\" && r.flow_id.length > 0 &&\n    typeof r.user_code === \"string\" && r.user_code.length > 0 &&\n    typeof r.verify_url === \"string\" && r.verify_url.length > 0\n  )\n}","tryCatchPattern":"try {\n  const resp = await loginOAuth({ provider: \"openai\", method: \"device_code\" })\n  if (!isDeviceLoginResponse(resp)) {\n    throw new Error(t(\"credentials.errors.invalidDeviceResponse\"))\n  }\n} catch (err) {\n  setError(err instanceof Error ? err.message : t(\"credentials.errors.loginFailed\"))\n}","preventionTips":["Only show the device-code button when the providers endpoint marks OpenAI device flow available","Backend: return a non-2xx with a JSON error instead of an empty 200 when device flow is unconfigured","Cover the device login response shape in API contract tests"],"tags":["oauth","openai","api-contract","frontend","device-flow"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}