BigPizzaV3/CodexPlusPlus · error · Error
会话接口返回了不匹配的数据
Error message
会话接口返回了不匹配的数据
What it means
graphFromExport validates the payload returned by the Codex++ session-read bridge before parsing it as a codex-rollout JSONL export. It throws '会话接口返回了不匹配的数据' when result.kind !== 'codex-rollout', result.session_id !== the requested threadId, or result.content is not a string. This guards the parser against the bridge returning a different payload shape (e.g. 'canvas-messages') or data for another thread.
Solutions
- Check result.kind returned by readCanvasSource and ensure the 'canvas-messages' branch (not graphFromExport) handles that shape
- Verify the bridge script and canvas.user.js are from the same version and the bridge still returns kind 'codex-rollout' with string content
- Re-open/refresh the canvas so threadId and the bridge's session_id are in sync
- Log result.session_id vs the passed threadId to find the mismatch source
Example fix
// before
const data = result.kind==='canvas-messages' ? fromNative(result) : graphFromExport(result,id,ann);
// after
if (result.kind==='codex-rollout' && result.session_id===id && typeof result.content==='string') {
data = graphFromExport(result,id,ann);
} else if (result.kind==='canvas-messages') { data = fromNative(result); } else { status('会话数据格式不受支持'); } Defensive patterns
Strategy: type-guard
Validate before calling
function isRolloutFor(r, threadId){ return !!r && r.status==='ok' && r.kind==='codex-rollout' && r.session_id===threadId && typeof r.content==='string'; }
if (!isRolloutFor(result, id)) { /* route to native branch or surface error */ } Type guard
const isRolloutExport = (r, threadId) => r?.status==='ok' && r.kind==='codex-rollout' && r.session_id===threadId && typeof r.content==='string';
Try / catch
try { data = graphFromExport(result, id, ann); } catch (e) { if (e.message==='会话接口返回了不匹配的数据') { status('会话数据格式不匹配,请刷新或检查 bridge 版本'); } else throw e; } Prevention
- Check result.kind before choosing the parse path (native vs rollout)
- Keep the userscript and bridge at matching versions
- Compare session_id with the current thread id before parsing
When it happens
Trigger: sync calls readCanvasSource and passes its result to graphFromExport when result.kind is not 'canvas-messages'; the throw fires if the bridge returned kind 'canvas-messages' or anything unexpected, session_id doesn't match the current thread id, or content is missing/non-string (e.g. bridge version mismatch or empty response).
Common situations: Running a newer/older bridge (readCanvasSource) that returns the native 'canvas-messages' shape but the userscript still routes it to graphFromExport; switching tasks between the request and the response so session_id no longer matches; a bridge failure returning a payload without content.
Related errors
- 会话接口返回了不匹配的数据
- 记录中的任务 ID 与当前任务不一致
- 记录中的任务 ID 与当前任务不一致
- result?.message||'Codex++ 会话读取失败'
- user-script runtime status must be a JSON object
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/ab13c0b428dbfb3e.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:88
if(!collapsed.has(row.node.id))for(let i=children.length-1;i>=0;i--)stack.push({node:children[i],depth:row.depth+1});
}
return {rows,pending:view.pending};
}
function treeMarkup(nodes,currentNodeId,collapsed=new Set(),selected=null,options={}){
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const view=visibleTreeRows(nodes,currentNodeId,collapsed),offset=options.offset||0,pendingOffset=options.pendingOffset||0;
const card=n=>`<button class="node ${selected===n.id?'selected':''} ${n.id===currentNodeId?'current':''}" data-node="${esc(n.id)}"><small>${esc(n.status)} · ${n.summaryKind==='原文摘录'?'原文摘录':'模型归纳'}${n.id===currentNodeId?' · 当前推进':''}</small><strong>${esc(n.title)}</strong><p>${esc(n.summary)}</p></button>`;
const tree=view.rows.length?`<ul class="task-tree" aria-label="模型识别的任务树">${view.rows.slice(offset,offset+100).map(({node:n,depth,childCount,active})=>`<li class="task-branch ${active?'active-path':''}" style="margin-left:${Math.min(depth,8)*14}px"><div class="tree-row">${childCount?`<button class="tree-toggle" data-collapse="${esc(n.id)}" aria-label="${collapsed.has(n.id)?'展开':'收起'} ${esc(n.title)}" aria-expanded="${!collapsed.has(n.id)}">${collapsed.has(n.id)?'▸':'▾'}</button>`:'<span class="tree-leaf" aria-hidden="true">·</span>'}${card(n)}</div>${depth>8?`<small>第 ${depth+1} 层 · 详情中可查看完整路径</small>`:''}</li>`).join('')}</ul>`:'<p class="note">点击「整理脉络」,由 Codex 识别目标、方案与多层尝试。</p>';
const pending=view.pending.length?`<details class="pending-messages" ${options.pendingOpen?'open':''}><summary>待整理消息 · ${view.pending.length} 条</summary><p class="note">任务归属将在下一次整理时识别。</p><div class="pending-list">${view.pending.slice(pendingOffset,pendingOffset+20).map(card).join('')}</div><div class="tree-actions"><button data-pending-page="${Math.max(0,pendingOffset-20)}" ${pendingOffset===0?'disabled':''}>前 20 条</button><span>${pendingOffset+1}–${Math.min(pendingOffset+20,view.pending.length)}</span><button data-pending-page="${pendingOffset+20}" ${pendingOffset+20>=view.pending.length?'disabled':''}>后 20 条</button></div></details>`:'';
return tree+pending;
}
function graphFromExport(result, threadId, annotations={nodes:[]}) {
if(result?.status!=='ok')throw new Error(result?.message||'Codex++ 会话读取失败');
if(result.kind!=='codex-rollout'||result.session_id!==threadId||typeof result.content!=='string')throw new Error('会话接口返回了不匹配的数据');
const messages=[],seen=new Set();
let declaredId=null;
// Ignore an unfinished JSONL tail; the next refresh will read the completed line.
const lines=result.content.slice(0,result.content.lastIndexOf('\n')+1).split('\n');
for(let i=0;i<lines.length;i++){
if(!lines[i].trim())continue;
let r;try{r=JSON.parse(lines[i]);}catch{continue;}
if(r.type==='session_meta')declaredId=r.payload?.id;
const m=parseMessage(r,`message-${threadId}-line-${i}`);
if(m&&!seen.has(m.id)){seen.add(m.id);messages.push(m);}
}
if(declaredId&&declaredId!==threadId)throw new Error('记录中的任务 ID 与当前任务不一致');
return {threadId,title:annotations.title||'当前对话',messages,...buildGraph(messages,annotations)};
}
function organizationPrompt(messages, requestId) {
const history=messages.map(({id,role,text})=>({id,role,text}));
if(JSON.stringify(history).length>240000)throw Error('当前对话过长,暂不支持一次整理;已有画布已保留。');View on GitHub (pinned to b1ed92e5e4)