{"record":{"id":"f0dda430236b304a","repo":"can1357/oh-my-pi","slug":"kilo-device-authorization-response-missing-require","errorCode":null,"errorMessage":"Kilo device authorization response missing required fields","messagePattern":"Kilo device authorization response missing required fields","errorType":"exception","errorClass":"AIError.OAuthError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/registry/kilo.ts","lineNumber":47,"sourceCode":"\t\t\tthrow new AIError.OAuthError(\"Too many pending authorization requests. Please try again later.\", {\n\t\t\t\tkind: \"polling\",\n\t\t\t\tprovider: \"kilo\",\n\t\t\t\tstatus: initiateResponse.status,\n\t\t\t});\n\t\t}\n\t\tthrow new AIError.OAuthError(`Failed to initiate device authorization: ${initiateResponse.status}`, {\n\t\t\tkind: \"device-auth\",\n\t\t\tprovider: \"kilo\",\n\t\t\tstatus: initiateResponse.status,\n\t\t});\n\t}\n\n\tconst initiateData = (await initiateResponse.json()) as KiloDeviceAuthCodeResponse;\n\tconst userCode = initiateData.code;\n\tconst verificationUrl = initiateData.verificationUrl;\n\tconst expiresInSeconds = initiateData.expiresIn;\n\tif (!userCode || !verificationUrl || typeof expiresInSeconds !== \"number\" || expiresInSeconds <= 0) {\n\t\tthrow new AIError.OAuthError(\"Kilo device authorization response missing required fields\", {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"kilo\",\n\t\t});\n\t}\n\n\tcallbacks.onAuth?.({\n\t\turl: verificationUrl,\n\t\tinstructions: `Enter code: ${userCode}`,\n\t});\n\n\tconst deadline = Date.now() + expiresInSeconds * 1000;\n\twhile (Date.now() < deadline) {\n\t\tif (callbacks.signal?.aborted) {\n\t\t\tthrow new AIError.LoginCancelledError();\n\t\t}\n\n\t\tconst pollResponse = await fetchImpl(`${KILO_DEVICE_AUTH_BASE_URL}/codes/${encodeURIComponent(userCode)}`);\n\t\tif (pollResponse.status === 202) {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/registry/kilo.ts#L29-L65","documentation":"After the initiation POST succeeds, loginKilo parses the JSON as KiloDeviceAuthCodeResponse and validates that code (userCode), verificationUrl, and expiresIn (a positive number) are all present. If any is missing or malformed, the provider's response doesn't match the expected device-auth contract, and this OAuthError (kind \"validation\") is thrown instead of continuing with a broken flow.","triggerScenarios":"Kilo's device-code endpoint returns 2xx with a JSON body lacking `code`, `verificationUrl`, or a numeric `expiresIn > 0` — e.g. an API schema change, an error payload served with 200, or a truncated/proxied response.","commonSituations":"Provider API updated its response shape while the client library is outdated; a captive portal or proxy returning HTML with 200 status; intermittent backend bug returning partial payloads; pointing the client at a staging endpoint with a different schema.","solutions":["Update the AI package to the latest version so the expected response schema matches the provider's current API.","Log the raw initiateResponse body to see what the server actually returned and confirm the schema mismatch.","Check for proxies/captive portals altering the response body.","If the server is a custom/staging endpoint, restore it to the documented device-auth response shape ({ code, verificationUrl, expiresIn })."],"exampleFix":"// before\nconst data = JSON.parse(body); // { verification_url: ... } renamed field -> validation error\n// after: update client or map the field\nconst data = JSON.parse(body);\nconst normalized = { code: data.code ?? data.device_code, verificationUrl: data.verificationUrl ?? data.verification_url, expiresIn: data.expiresIn ?? data.expires_in };","handlingStrategy":"validation","validationCode":"// pre-validate the shape you expect from device-code initiation before handing to the flow\ndefense: // before calling loginKilo, ensure you're on a current client version\nif (!KILO_DEVICE_AUTH_BASE_URL || !KILO_DEVICE_AUTH_BASE_URL.startsWith(\"https://\")) {\n  throw new Error(\"Kilo device-auth base URL misconfigured\");\n}","typeGuard":"function isKiloDeviceAuthResponse(v: unknown): v is KiloDeviceAuthCodeResponse {\n  const r = v as KiloDeviceAuthCodeResponse;\n  return typeof r?.code === \"string\" && r.code.length > 0\n    && typeof r?.verificationUrl === \"string\" && r.verificationUrl.startsWith(\"https://\")\n    && typeof r?.expiresIn === \"number\" && r.expiresIn > 0;\n}","tryCatchPattern":"try {\n  await loginKilo(callbacks);\n} catch (err) {\n  if (err instanceof AIError.OAuthError && err.message.includes(\"missing required fields\")) {\n    console.error(\"Kilo API response shape changed — update the AI package or check for a proxy altering responses\");\n  } else throw err;\n}","preventionTips":["Keep the AI package updated against Kilo API schema changes.","Bypass suspicious proxies/captive portals when logging in.","Log raw response bodies on login failure to diagnose contract drift.","Don't point the client at staging endpoints with different schemas."],"tags":["oauth","kilo","device-flow","schema-validation","api-contract"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}