BigPizzaV3/CodexPlusPlus · error · Error
历史轮次分页游标重复
Error message
历史轮次分页游标重复
What it means
readNativeHistory paginates thread/turns/list with a cursor, tracking every cursor value in turnCursors to detect server-side pagination loops. Because the first cursor is null, a repeated null means the server keeps returning a page without nextCursor; a repeated non-null cursor means the server loops. The read aborts to prevent an infinite loop and duplicated history.
Solutions
- Update the plugin/Codex so the pagination protocol matches the backend implementation
- Inspect the raw thread/turns/list responses for a protocol change (e.g. renamed nextCursor field)
- Add a page-count cap in addition to cursor tracking if very large threads are expected
- Retry the history read once — a transient backend glitch can return a duplicated cursor
Example fix
// before
if(turnCursors.has(cursor))throw Error('历史轮次分页游标重复');
// after
if(turnCursors.has(cursor))break; // treat as end of pagination
if(turns.length>MAX_TURNS)throw Error('历史轮次超出上限'); Defensive patterns
Strategy: retry
Validate before calling
const seen=new Set(); let cursor=null, guard=0;
while(guard++<100){ if(seen.has(cursor)) return /* stop early */; seen.add(cursor); cursor=await fetchNextCursor(cursor); if(cursor==null) break; } Type guard
function isWellFormedTurnsPage(page){ return !!page && Array.isArray(page.data) && (page.nextCursor===undefined||page.nextCursor===null||typeof page.nextCursor==='string'); } Try / catch
try { history=await readNativeHistory(send,threadId) } catch(e){ if(e.message==='历史轮次分页游标重复'){ history=await readNativeHistory(send,threadId,new Map()); } else throw e; } Prevention
- Cap pagination iterations in addition to cursor tracking
- Verify nextCursor handling against the backend's actual protocol
- Retry once on transient pagination glitches before failing the read
When it happens
Trigger: thread/turns/list repeatedly returns the same nextCursor (including null/undefined while more data exists), causing the do/while loop to see a cursor it has already visited.
Common situations: Native Codex backend bug or protocol change where page.nextCursor is undefined despite more turns existing, or a server returning a stable cursor for the last page repeatedly.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/4f2080f266e0e4be.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:403
})().catch(error=>{pending=null;throw error;});
return pending;
};
}
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;View on GitHub (pinned to b1ed92e5e4)