BigPizzaV3/CodexPlusPlus · error · Error
历史消息接口返回格式无效
Error message
历史消息接口返回格式无效
What it means
Each 'thread/items/list' page must contain an array in 'data'; the script validates Array.isArray(page?.data) before iterating rows. A null page or non-array data means the response shape is unrecognized, so it throws rather than processing garbage.
Solutions
- Update canvas.user.js for the current Codex version (response schema likely changed).
- Log the raw page object to see the new response shape and adapt parsing.
- Check that the 'decoded message length too large' retry loop exhausted limits correctly — an overly large page may come back malformed; retry with a smaller limit.
- Retry history import after fully reloading the Codex app.
Example fix
// before
if(!Array.isArray(page?.data))throw Error('历史消息接口返回格式无效');
// after
if(!Array.isArray(page?.data)){console.warn('unexpected items page',page);page={data:[],nextCursor:null};} Defensive patterns
Strategy: type-guard
Validate before calling
const page=await send('thread/items/list',...);
if(!page||!Array.isArray(page.data))throw new Error('malformed items page'); Type guard
const isItemsPage=p=>p!=null&&Array.isArray(p.data);
Try / catch
try{await importTurnItems(turn);}catch(e){if(e.message==='历史消息接口返回格式无效'){await sleep(500);return importTurnItems(turn,{smallerLimit:true});}throw e;} Prevention
- Validate response shape at one choke point before iterating
- Reduce page limit when payloads are large
- Reload the app after Codex updates before importing history
- Keep a schema snapshot per Codex version in tests
When it happens
Trigger: 'thread/items/list' returns null/undefined page, or data missing/not an array — typically after a Codex API schema change, an error swallowed upstream by send(), or a malformed RPC envelope.
Common situations: Codex client update renamed the items-list response wrapper; transient RPC failure surfaced as empty payload; hitting the internal API with wrong parameter shape after version drift.
Related errors
- 历史消息缺少稳定 ID
- 历史轮次缺少 ID
- API 未能生成本批整理结果
- API 输出达到长度上限,本批未提交;请使用输出容量更大的模型
- API 未返回有效的 choices[0].message.content,请确认兼容 Chat Completions
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/4365f7ddf61ca6a0.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:434
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();
if(cursors.has(itemCursor))throw Error('历史消息分页游标重复');
let page;
for(;;){
try{page=await send('thread/items/list',{threadId,turnId:turn.id,cursor:itemCursor,limit,sortDirection:'asc'});break;}
catch(error){
if(limit>1&&/decoded message length too large/i.test(error.message)){limit=Math.max(1,Math.floor(limit/2));continue;}
throw error;
}
}
if(!Array.isArray(page?.data))throw Error('历史消息接口返回格式无效');
cursors.add(itemCursor);
for(const row of page.data){
if(row.turnId!=null&&row.turnId!==turn.id)throw Error('历史消息所属轮次不匹配');
const item=row.item??row;
if(!['userMessage','agentMessage'].includes(item.type))continue;
if(typeof item.id!=='string')throw Error('历史消息缺少稳定 ID');
const role=item.type==='userMessage'?'user':'assistant';
const content=role==='user'?(item.content??[]):[{text:item.text??''}];
const message=parseMessage({type:'response_item',payload:{type:'message',id:item.id,role,phase:role==='assistant'?(item.phase??'final_answer'):'',content}},item.id);
if(message)parsed.push({...message,turnId:turn.id,turnStatus:turn.status});
}
itemCursor=page.nextCursor??null;
onProgress(`正在读取历史 ${index+1}/${turns.length} · 已读 ${parsed.length} 条消息…`);
if(turn.status==='completed'&&itemCursor!==null)await cache.set(`${key}:partial`,{messages:parsed,cursor:itemCursor});
}while(itemCursor!==null);
if(turn.status==='completed'){await cache.set(key,parsed);await cache.delete(`${key}:partial`);}
}
// Older completed-turn caches contain text and IDs but no turn metadata.View on GitHub (pinned to b1ed92e5e4)