{"record":{"id":"65348606e40c7d8a","repo":"BigPizzaV3/CodexPlusPlus","slug":"bridge-data","errorCode":null,"errorMessage":"会话接口返回了不匹配的数据","messagePattern":"会话接口返回了不匹配的数据","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/bridge-data.mjs","lineNumber":5,"sourceCode":"import {parseMessage,buildGraph} from './model.mjs';\n\nexport function graphFromExport(result, threadId, annotations={nodes:[]}) {\n  if(result?.status!=='ok')throw new Error(result?.message||'Codex++ 会话读取失败');\n  if(result.kind!=='codex-rollout'||result.session_id!==threadId||typeof result.content!=='string')throw new Error('会话接口返回了不匹配的数据');\n  const messages=[],seen=new Set();\n  let declaredId=null;\n  // Ignore an unfinished JSONL tail; the next refresh will read the completed line.\n  const lines=result.content.slice(0,result.content.lastIndexOf('\\n')+1).split('\\n');\n  for(let i=0;i<lines.length;i++){\n    if(!lines[i].trim())continue;\n    let r;try{r=JSON.parse(lines[i]);}catch{continue;}\n    if(r.type==='session_meta')declaredId=r.payload?.id;\n    const m=parseMessage(r,`message-${threadId}-line-${i}`);\n    if(m&&!seen.has(m.id)){seen.add(m.id);messages.push(m);}\n  }\n  if(declaredId&&declaredId!==threadId)throw new Error('记录中的任务 ID 与当前任务不一致');\n  return {threadId,title:annotations.title||'当前对话',messages,...buildGraph(messages,annotations)};\n}\n","sourceCodeStart":1,"sourceCodeEnd":20,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/bridge-data.mjs#L1-L20","documentation":"In bridge-data.mjs's copy of graphFromExport, the second guard throws '会话接口返回了不匹配的数据' when result.kind !== 'codex-rollout', session_id doesn't match threadId, or content isn't a string. It protects the JSONL parser from unexpected payload shapes.","triggerScenarios":"sync passes a successful result whose kind is 'canvas-messages' (native shape) to graphFromExport, whose session_id differs from the requested thread, or whose content is missing/non-string.","commonSituations":"Bridge version skew changing the payload kind; native paginated read path returning canvas-messages; thread switched between request and response; truncated response without content.","solutions":["Route kind==='canvas-messages' results to the native branch instead of graphFromExport","Align bridge-data.mjs and the host so codex-rollout results carry string content and matching session_id","Re-sync so session_id and threadId agree","Assert the shape in tests before parsing"],"exampleFix":"// before\nif(result.kind!=='codex-rollout'||result.session_id!==threadId||typeof result.content!=='string')throw new Error('会话接口返回了不匹配的数据');\n// after\nif (result.kind==='canvas-messages') return fromNativeMessages(result, threadId, annotations);\nif(result.kind!=='codex-rollout'||result.session_id!==threadId||typeof result.content!=='string')throw new Error('会话接口返回了不匹配的数据');","handlingStrategy":"type-guard","validationCode":"const matches = (r, threadId) => r?.status==='ok' && r.kind==='codex-rollout' && r.session_id===threadId && typeof r.content==='string';\nif (!matches(result, threadId)) throw new Error('会话接口返回了不匹配的数据');","typeGuard":"const isRolloutForThread = (r, threadId) => r?.kind==='codex-rollout' && r.session_id===threadId && typeof r.content==='string';","tryCatchPattern":"try { graph = graphFromExport(result, threadId, ann); } catch (e) { if (e.message==='会话接口返回了不匹配的数据') fallbackToNativeOrRefresh(); else throw e; }","preventionTips":["Dispatch on result.kind before calling the parser","Assert payload shape in bridge integration tests","Re-sync when threadId changes mid-request"],"tags":["validation","data-shape","runtime"],"backgroundTag":"unexpected-response-shape","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}