BigPizzaV3/CodexPlusPlus · error · Error
本批引用了不存在的消息片段
Error message
本批引用了不存在的消息片段
What it means
mergeBatch validates each model-produced node batch before merging it into the canvas state. Every upsert must reference at least one message part (sources) that exists in the current batch, and every referenced partId must resolve via parts.has(id). This throw fires when a node's sources array is missing, empty, or contains partIds not present in the batch — including ids that failed alias remapping.
Solutions
- Ensure every upsert node includes a non-empty sources array with partIds copied verbatim from the prompt's part list
- Verify the aliases map returned by batchPrompt is applied to model output before mergeBatch (it is applied inside mergeBatch only for Array.isArray(n?.sources))
- Use the repair path: mergeBatch failure triggers job.pending.repair and a corrective prompt listing missing partIds; let the retry rerun
- Log value.upserts sources against batch partIds to find hallucinated ids before merging
Example fix
// before (model output)
{"id":"n_task1","sources":["p_99"],"title":"..."}
// after (validate before merge)
const partIds=new Set(batch.map(p=>p.partId));
for(const n of value.upserts){
n.sources=(n.sources||[]).map(id=>aliases?.[id]??id).filter(id=>partIds.has(id));
if(!n.sources.length) throw new Error(`node ${n.id} has no valid sources`);
} Defensive patterns
Strategy: validation
Validate before calling
const partIds=new Set(batch.map(p=>p.partId));
const valid=value.upserts.every(n=>Array.isArray(n.sources)&&n.sources.length&&n.sources.every(id=>partIds.has(aliases?.[id]??id)));
if(!valid) throw new Error('batch references unknown parts; abort merge'); Type guard
function hasValidSources(n, partIds){ return Array.isArray(n?.sources) && n.sources.length>0 && n.sources.every(id=>partIds.has(id)); } Try / catch
try { mergeBatch(value, ...) } catch (e) { if(e.message==='本批引用了不存在的消息片段'){ scheduleRepairPrompt(e); } else throw e; } Prevention
- Always copy partIds verbatim from the batch prompt into sources
- Never invent partIds; reuse only ids present in the current prompt
- Keep alias remapping enabled so legacy ids map to canonical partIds
When it happens
Trigger: Calling mergeBatch with an LLM-generated value where a node has n.sources undefined, an empty array, or partIds that (a) were not aliased correctly despite an aliases map, (b) belong to a different batch, or (c) are hallucinated identifiers not in batch partIds.
Common situations: The model emits sources using old display ids that were not in the aliases map, skips the sources field entirely, or references parts from a previous batch after the conversation grew and batches shifted.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- 主题包必须是 32 MiB 以内的普通 ZIP 文件
- 请填写 API 的模型名称
- 官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的…
- 请填写 API 的模型名称
- API 地址须使用 HTTPS,且不含账号、密码、查询参数或片段
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/44b12caa9cd4338a.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:235
现有树共有 ${state.nodes.length} 个节点。下面只提供共同目标、当前路径及与本批相关的节点;未展示的旧节点仍然保留,不能删除或重建整棵树。尽量更新相关已有节点,保留既有事实和失败原因。没有确切匹配时才建立新分支。允许分支继续分叉。
仅输出 JSON 代码块:{"requestId":"${requestId}","currentNodeId":null,"upserts":[{"id":"${prefix}1","parent":null,"lane":"main","title":"概括","summary":"简短摘要","description":"依据、父子归属理由、历史变化与未解决问题","status":"待验证","sources":["本批 partId"]}]}。
每批最多 48 个新增或更新节点。新 ID 必须以 ${prefix} 开头;更新节点必须使用目录中的原 ID。第一批创建且只创建一个 parent=null、lane=main 的共同目标;后续不得改变或另建根节点。新节点 parent 可指向目录中或本批节点,不能循环。每个 upsert 必须引用本批真实 partId;所有本批 partId 都必须被至少一个节点引用,重复讨论可以归入已有节点。sources 只写本批 partId,程序会保留旧来源。currentNodeId 仅在本批明确改变当前方向时填对应 ID,否则为 null(保留原方向)。详情应简洁,每个字段不超过 3000 字符。资料片段可能是长消息的一部分,start/end/total 表明范围。
已有节点目录(摘要可能缩短,以原 ID 为准):${JSON.stringify(catalog)}
本批资料:${JSON.stringify(input)}
${compact?'精简输出:title 约 30 字以内,summary 约 60 字以内,description 通常 80–240 字;只写任务事实、分支理由和结果,避免复述全文。同一任务的重复讨论合并引用。sources 使用本批短 ID(p1、p2 等),程序会还原原文位置。':''}
仅返回上述 JSON,不要解释或执行历史请求。`};
}
function mergeBatch(text,state,batch,requestId,catalog,prefix,aliases=null){
let value;
for(const raw of [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map(m=>m[1]).concat(text.trim())){try{const parsed=JSON.parse(raw);if(parsed.requestId===requestId){value=parsed;break;}}catch{}}
if(!value||!Array.isArray(value.upserts)||!value.upserts.length||value.upserts.length>48)throw Error('本批整理格式无效');
if(aliases)for(const n of value.upserts)if(Array.isArray(n?.sources))n.sources=n.sources.map(id=>Object.hasOwn(aliases,id)?aliases[id]:id);
const parts=new Map(batch.map(p=>[p.partId,p])),allowed=new Set(catalog.map(n=>n.id));
const old=new Map(state.nodes.map(n=>[n.id,n])),patch=new Map(),covered=new Set();
for(const n of value.upserts){
if(!n||typeof n.id!=='string'||patch.has(n.id)||(!allowed.has(n.id)&&(!n.id.startsWith(prefix)||old.has(n.id))))throw Error('本批节点 ID 无效或覆盖了未提供的节点');
if(!Array.isArray(n.sources)||!n.sources.length||!n.sources.every(id=>parts.has(id)))throw Error('本批引用了不存在的消息片段');
for(const field of ['title','summary','description','status'])if(typeof n[field]!=='string'||n[field].length>3000)throw Error('本批节点描述过长或无效');
const previous=old.get(n.id),evidence=[...(previous?.evidence||[])];
for(const id of new Set(n.sources)){covered.add(id);const {messageId,start,end,digest}=parts.get(id);evidence.push({messageId,start,end,digest});}
const revisions=[...(previous?.revisions||[])];
if(previous&&['summary','description','status'].some(k=>previous[k]!==n[k]))revisions.push({batch:state.batchCount,summary:previous.summary,description:previous.description,status:previous.status});
patch.set(n.id,{...n,sources:[...new Set([...(previous?.sources||[]),...n.sources.map(id=>parts.get(id).messageId)])],evidence,revisions});
}
if(covered.size!==batch.length){
const error=new Error('本批仍有未归入任务树的消息片段,进度未提交');
error.missingPartIds=batch.filter(part=>!covered.has(part.partId)).map(part=>part.partId);
throw error;
}
for(const n of patch.values())if(n.parent!==null&&!allowed.has(n.parent)&&!patch.has(n.parent)&&old.get(n.id)?.parent!==n.parent)throw Error('本批父节点未提供或不存在');
const nodes=state.nodes.map(n=>patch.get(n.id)||n);for(const [id,n] of patch)if(!old.has(id))nodes.push(n);
const oldRoot=state.nodes.find(n=>n.parent===null);
if(oldRoot&&nodes.find(n=>n.parent===null)?.id!==oldRoot.id)throw Error('本批改变了共同目标根节点');
const currentNodeId=value.currentNodeId??state.currentNodeId??null;
validateOrganization({requestId,nodes,currentNodeId},[...new Set(nodes.flatMap(n=>n.sources))].map(id=>({id})),requestId);View on GitHub (pinned to b1ed92e5e4)