{"record":{"id":"5b96db07a7cfcd0d","repo":"BigPizzaV3/CodexPlusPlus","slug":"id-native-history","errorCode":null,"errorMessage":"历史轮次缺少 ID","messagePattern":"历史轮次缺少 ID","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/native-history.mjs","lineNumber":19,"sourceCode":"import {parseMessage} from './model.mjs';\n\nexport function isExportSizeError(message){\n  return /超过.*(?:大小|限制)|too (?:large|big)|size.{0,30}limit|maximum.{0,20}size/i.test(String(message));\n}\n\n// Wire contracts verified in the installed Desktop's history loader:\n// thread/turns/list(itemsView=notLoaded), then thread/items/list per turn.\nexport async function readNativeHistory(send,threadId,cache=new Map(),onProgress=()=>{},signal){\n  const turns=[],turnIds=new Set(),turnCursors=new Set();\n  let cursor=null;\n  do{\n    signal?.throwIfAborted();\n    if(turnCursors.has(cursor))throw Error('历史轮次分页游标重复');\n    turnCursors.add(cursor);\n    const page=await send('thread/turns/list',{threadId,cursor,limit:20,itemsView:'notLoaded',sortDirection:'asc'});\n    if(!Array.isArray(page?.data))throw Error('历史轮次接口返回格式无效');\n    for(const turn of page.data){\n      if(typeof turn.id!=='string')throw Error('历史轮次缺少 ID');\n      if(!turnIds.has(turn.id)){turnIds.add(turn.id);turns.push(turn);}\n    }\n    cursor=page.nextCursor??null;\n    onProgress(`正在读取轮次目录 · ${turns.length} 轮…`);\n  }while(cursor!==null);\n  const messages=[],seen=new Set();\n  for(const [index,turn] of turns.entries()){\n    signal?.throwIfAborted();onProgress(`正在读取历史 ${index+1}/${turns.length}…`);\n    const key=`${threadId}:${turn.id}`,cached=await cache.get(key);\n    let parsed;\n    if(cached&&turn.status==='completed')parsed=cached;\n    else{\n      const partial=turn.status==='completed'?await cache.get(`${key}:partial`):null;\n      parsed=partial?.messages?.slice()||[];let itemCursor=partial?.cursor??null,limit=32;const cursors=new Set();\n      do{\n        signal?.throwIfAborted();\n        if(cursors.has(itemCursor))throw Error('历史消息分页游标重复');\n        let page;","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/native-history.mjs#L1-L37","documentation":"readNativeHistory paginates thread/turns/list with a cursor; this throw fires when a returned page's turn(s) lack an id. Turn ids are the keys for the per-turn thread/items/list follow-ups and the dedupe sets (turnIds/turnCursors), so an id-less turn cannot be processed and the whole history read aborts.","triggerScenarios":"Backend returns a turn object without id, or with a numeric/null id; partial/corrupted turn records in a page.","commonSituations":"Protocol version drift where turn records changed shape; corrupted session storage in the codex app; custom backend implementations omitting id.","solutions":["Skip or log-and-skip malformed turns instead of aborting, if acceptable","Upgrade the codex backend to a version emitting string turn ids","Inspect the raw page.data to identify the malformed turn","Validate threadId points at a healthy thread; try a different thread"],"exampleFix":"// before\nif(typeof turn.id!=='string')throw Error('历史轮次缺少 ID');\n// after\nif(typeof turn.id!=='string'){ console.warn('skip turn without id', turn); continue; }","handlingStrategy":"validation","validationCode":"const bad = page.data.filter(t => typeof t?.id !== 'string');\nif (bad.length) console.warn('turns without id', bad);","typeGuard":"const hasTurnId = t => typeof t?.id === 'string' && t.id.length > 0;","tryCatchPattern":"try { await readNativeHistory(send, threadId) } catch (e) { if (e.message === '历史轮次缺少 ID') { skipBadTurnsAndRetry(); } else throw e; }","preventionTips":["Sanitize turn pages with a filter before processing","Keep backend and client protocol versions aligned","Log and skip malformed records when partial history is acceptable","Validate storage health if corruption is suspected"],"tags":["api","data-integrity","pagination"],"backgroundTag":"missing-required-argument","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"}