BigPizzaV3/CodexPlusPlus · error · Error

历史轮次接口返回格式无效

Error message

历史轮次接口返回格式无效

What it means

readNativeHistory requires each thread/turns/list response to carry a data array of turns. This throw fires when the response is null/undefined or page.data is not an array, indicating the native API response does not match the expected thread/turns/list(itemsView=notLoaded) shape.

Solutions

  1. Update the plugin so its adapter mapping matches the installed Codex API schema
  2. Log the raw page response to identify the new envelope shape and adapt parsing
  3. Verify the threadId is valid and the thread still exists
  4. Retry the read — a transient RPC failure can return a null/empty response

Example fix

// before
if(!Array.isArray(page?.data))throw Error('历史轮次接口返回格式无效');
// after
const data=page?.data ?? page?.turns;
if(!Array.isArray(data))throw Object.assign(Error('历史轮次接口返回格式无效'),{cause:page});
Defensive patterns

Strategy: type-guard

Validate before calling

const page=await send('thread/turns/list',{threadId,cursor,limit:20,itemsView:'notLoaded',sortDirection:'asc'});
if(!page||typeof page!=='object'||!Array.isArray(page.data)) throw new Error('malformed turns page');

Type guard

function isTurnsPage(p){ return !!p && Array.isArray(p.data) && p.data.every(t=>typeof t?.id==='string'); }

Try / catch

try { history=await readNativeHistory(send,threadId) } catch(e){ if(e.message==='历史轮次接口返回格式无效'){ logRawResponse(e.cause); retryOrAbort(); } else throw e; }

Prevention

When it happens

Trigger: send('thread/turns/list',{...}) resolves with null, an error object, or an object whose data field is missing or not an array — e.g. backend returns {error:...}, or a protocol version changed the response envelope.

Common situations: Codex app updated with a changed RPC schema while the plugin adapter still expects the old shape; transient RPC failure surfaced as a null response; wrong threadId causing an error-shaped payload.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/665975d139df9b8f. Report an issue: GitHub.

Appendix: source

Thrown at tools/conversation-canvas/public/canvas.user.js:406

}
const loadNativeRuntime=createNativeLoader();


function isExportSizeError(message){
  return /超过.*(?:大小|限制)|too (?:large|big)|size.{0,30}limit|maximum.{0,20}size/i.test(String(message));
}

// Wire contracts verified in the installed Desktop's history loader:
// thread/turns/list(itemsView=notLoaded), then thread/items/list per turn.
async function readNativeHistory(send,threadId,cache=new Map(),onProgress=()=>{},signal){
  const turns=[],turnIds=new Set(),turnCursors=new Set();
  let cursor=null;
  do{
    signal?.throwIfAborted();
    if(turnCursors.has(cursor))throw Error('历史轮次分页游标重复');
    turnCursors.add(cursor);
    const page=await send('thread/turns/list',{threadId,cursor,limit:20,itemsView:'notLoaded',sortDirection:'asc'});
    if(!Array.isArray(page?.data))throw Error('历史轮次接口返回格式无效');
    for(const turn of page.data){
      if(typeof turn.id!=='string')throw Error('历史轮次缺少 ID');
      if(!turnIds.has(turn.id)){turnIds.add(turn.id);turns.push(turn);}
    }
    cursor=page.nextCursor??null;
    onProgress(`正在读取轮次目录 · ${turns.length} 轮…`);
  }while(cursor!==null);
  const messages=[],seen=new Set();
  for(const [index,turn] of turns.entries()){
    signal?.throwIfAborted();onProgress(`正在读取历史 ${index+1}/${turns.length}…`);
    const key=`${threadId}:${turn.id}`,cached=await cache.get(key);
    let parsed;
    if(cached&&turn.status==='completed')parsed=cached;
    else{
      const partial=turn.status==='completed'?await cache.get(`${key}:partial`):null;
      parsed=partial?.messages?.slice()||[];let itemCursor=partial?.cursor??null,limit=32;const cursors=new Set();
      do{
        signal?.throwIfAborted();

View on GitHub (pinned to b1ed92e5e4)