BigPizzaV3/CodexPlusPlus · error
历史轮次接口返回格式无效
Error message
历史轮次接口返回格式无效
What it means
readNativeHistory expects each thread/turns/list page to be an object with an array data field. This throw means the response lacked page.data or it was not an array, indicating an unexpected response shape from the turns API.
Solutions
- Log the raw page response to see what the backend actually returned
- Verify threadId is valid and the codex app/protocol version supports thread/turns/list with itemsView
- Check that send() is wired to the real codex RPC channel, not a stale mock
- Retry after restarting the codex backend
Example fix
// before
const page = await send('thread/turns/list', ...); // page undefined
// after
if (!page || !Array.isArray(page?.data)) throw new Error('turns page invalid: ' + JSON.stringify(page)); Defensive patterns
Strategy: type-guard
Validate before calling
const page = await send('thread/turns/list', args);
if (!page || !Array.isArray(page.data)) throw new Error('bad turns page'); Type guard
const isTurnsPage = p => Boolean(p) && Array.isArray(p.data);
Try / catch
try { await readNativeHistory(send, threadId) } catch (e) { if (e.message === '历史轮次接口返回格式无效') { inspectAndReconnect(); } else throw e; } Prevention
- Confirm the RPC method name matches your codex protocol version
- Validate threadId before listing turns
- Log raw responses when integrating new backends
- Wrap send() to normalize error envelopes into pages
When it happens
Trigger: send('thread/turns/list') returns null/undefined, an error envelope, or a data field that is not an array.
Common situations: Wrong RPC method name or threadId for the installed codex version; backend returned an error object instead of a page; middleware mangling responses.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/038c318ebcc9f888.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/native-history.mjs:17
import {parseMessage} from './model.mjs';
export 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.
export 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)