{"record":{"id":"d1dfacb7f80eb330","repo":"jlcodes99/cockpit-tools","slug":"claude-oauth-start","errorCode":null,"errorMessage":"Claude OAuth start 响应缺少关键字段","messagePattern":"Claude OAuth start 响应缺少关键字段","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/claudeService.ts","lineNumber":59,"sourceCode":"    throw new Error('Claude login start 响应缺少关键字段');\n  }\n\n  return {\n    loginId,\n    userDataDir,\n    expiresIn: Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 1800,\n    intervalSeconds: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds : 2,\n  };\n}\n\nfunction normalizeClaudeOAuthStartResponse(raw: ClaudeOAuthStartResponseRaw): ClaudeOAuthStartResponse {\n  const loginId = raw.loginId ?? raw.login_id ?? '';\n  const verificationUri = raw.verificationUri ?? raw.verification_uri ?? '';\n  const expiresIn = Number(raw.expiresIn ?? raw.expires_in ?? 0);\n  const intervalSeconds = Number(raw.intervalSeconds ?? raw.interval_seconds ?? 0);\n\n  if (!loginId || !verificationUri) {\n    throw new Error('Claude OAuth start 响应缺少关键字段');\n  }\n\n  return {\n    loginId,\n    verificationUri,\n    expiresIn: Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 600,\n    intervalSeconds: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds : 1,\n  };\n}\n\nexport async function listClaudeAccounts(): Promise<ClaudeAccount[]> {\n  return await invoke('list_claude_accounts');\n}\n\nexport async function deleteClaudeAccount(accountId: string): Promise<void> {\n  return await invoke('delete_claude_account', { accountId });\n}\n","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/services/claudeService.ts#L41-L77","documentation":"normalizeClaudeOAuthStartResponse validates the Claude OAuth start response and throws when loginId or verificationUri (snake_case variants accepted) is missing or empty. Both are required to continue the device-authorization flow; expiry and interval get defaults.","triggerScenarios":"The backend OAuth start/prepare command returns a payload without a usable loginId or verificationUri/verification_uri field.","commonSituations":"OAuth provider returning an error body that the backend forwards verbatim, backend/DTO field-name mismatch after a refactor, network or auth-server outage producing an incomplete response.","solutions":["Log the raw invoke response to identify the missing field","Confirm the Rust OAuth start command maps the provider response's verification_uri into verificationUri and returns a loginId","Check Claude OAuth endpoint availability and credentials (client ID) if the provider returned an error instead","Align frontend normalization with any renamed backend fields"],"exampleFix":"// before\n// backend forwarded provider error body { \"error\": \"invalid_client\" }\n// after\n// backend: map errors to Err and only resolve Ok with { loginId, verificationUri, ... }","handlingStrategy":"try-catch","validationCode":"const raw = /* backend OAuth response */;\nconst loginId = raw?.loginId ?? raw?.login_id ?? '';\nconst verificationUri = raw?.verificationUri ?? raw?.verification_uri ?? '';\nif (!loginId || !verificationUri) throw new Error('Claude OAuth start 响应缺少关键字段');","typeGuard":"function isValidOauthStart(r: unknown): r is { loginId: string; verificationUri: string } {\n  const o = r as any;\n  return !!(o && (o.loginId || o.login_id) && (o.verificationUri || o.verification_uri));\n}","tryCatchPattern":"try {\n  const start = await claudeOauthLoginStart();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('缺少关键字段')) {\n    showOauthError('OAuth start failed: incomplete response — check backend/provider.');\n  } else throw e;\n}","preventionTips":["Assert verificationUri is mapped from the provider's verification_uri in backend tests","Check Claude OAuth endpoint health and client credentials before starting the flow","Forward provider error bodies as errors, never as partial payloads","Version the backend/frontend DTO contract and test both camelCase and snake_case variants"],"tags":["validation","oauth","claude","network"],"backgroundTag":"missing-response-field","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}