BigPizzaV3/CodexPlusPlus · error · Error

本批父节点未提供或不存在

Error message

本批父节点未提供或不存在

What it means

After merging node fields, mergeBatch validates each node's parent reference: parent must be null (root), an id in the catalog's allowed set, an id present in this batch's patch, or match the node's previous parent. This throw fires when a node points at a parent id that is none of those — i.e., the parent node was not provided in this batch and does not exist in the allowed catalog.

Solutions

  1. Constrain the prompt: parents must be null, catalog ids, or nodes within the same batch upsert
  2. Pre-validate parent ids against allowed ∪ patch keys before calling mergeBatch and fall back to null or a catalog id
  3. Use the repair retry path so the model re-emits the batch with valid parents
  4. If cross-batch parents are needed, first create the parent node in an earlier batch, then reference it by its catalog id

Example fix

// before
{n.id:'n_x', parent:'n_ghost', ...}
// after (pre-check)
const allowed=new Set(catalog.map(n=>n.id));
const patchIds=new Set(value.upserts.map(n=>n.id));
for(const n of value.upserts){
  if(n.parent!==null && !allowed.has(n.parent) && !patchIds.has(n.parent)) n.parent=null;
}
Defensive patterns

Strategy: validation

Validate before calling

const allowed=new Set(catalog.map(n=>n.id));
const inBatch=new Set(value.upserts.map(n=>n.id));
for(const n of value.upserts)
  if(n.parent!==null&&!allowed.has(n.parent)&&!inBatch.has(n.parent)) n.parent=null;

Type guard

function hasValidParent(n, allowed, inBatch){ return n.parent===null || allowed.has(n.parent) || inBatch.has(n.parent); }

Try / catch

try { mergeBatch(value,...) } catch(e){ if(e.message==='本批父节点未提供或不存在'){ resetInvalidParents(); retryMerge(); } else throw e; }

Prevention

When it happens

Trigger: Model output sets n.parent to a fabricated id, an id from a different batch, or renames a parent without supplying the parent's upsert; also occurs when the model re-parents an existing node to a new node id not in the current patch.

Common situations: Multi-batch organization where the model builds cross-batch hierarchies referencing nodes created in other batches; hallucinated parent ids after alias remapping.

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


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/bd2e1b8010e812a6. Report an issue: GitHub.

Appendix: source

Thrown at tools/conversation-canvas/public/canvas.user.js:248

  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);
  return {...state,schemaVersion:2,nodes,currentNodeId,batchCount:state.batchCount+1,done:{...state.done,...Object.fromEntries(batch.map(p=>[p.partId,true]))}};
}

// Save is an atomic durable checkpoint. A failed model call never commits its
// coverage; a restart can resume its pending side-chat turn before resubmitting.
async function organizeLong({messages,record,save,run,signal,progress=()=>{},onGraph=()=>{},compact=false}){
  const history=await prepareHistory(messages,signal);
  const partIndex=new Map(history.parts.map(part=>[part.partId,part]));
  const previous=record?.published;
  let job=record?.job;
  if(!job||!compatible(job.state.manifest,history.manifest)){
    const incremental=previous?.done&&compatible(previous.manifest,history.manifest);
    const state=incremental?{...previous,manifest:history.manifest}:{schemaVersion:2,nodes:[],currentNodeId:null,batchCount:0,done:{},manifest:history.manifest};

View on GitHub (pinned to b1ed92e5e4)