BigPizzaV3/CodexPlusPlus · error · Error

任务树包含不存在的父节点

Error message

任务树包含不存在的父节点

What it means

validateOrganization checks each organized node's sources against the ids of existing conversation messages; this throw means a node referenced a source id that is not among them. It is an LLM-output validation guard for the canvas organizer — the model hallucinated or mistyped a parent/source reference, so the whole organization result is rejected.

Solutions

  1. Ensure every non-null parent string equals the id of a node in the same result
  2. Remove or re-point dangling parent references to existing node IDs
  3. Re-run the organize request instructing the model to only reference IDs it defines
  4. Check for typos or stale IDs from previous runs

Example fix

// before
{"id":"b","parent":"old-root-1"}  // no such node
// after
{"id":"b","parent":"root"}  // existing node id
Defensive patterns

Strategy: validation

Validate before calling

const byId = new Set(nodes.map(n => n.id));
for (const n of nodes) if (n.parent !== null && !byId.has(n.parent)) console.warn('dangling parent', n.id, '->', n.parent);

Type guard

function parentsExist(nodes) {
  const ids = new Set(nodes.map(n => n.id));
  return nodes.every(n => n.parent === null || ids.has(n.parent));
}

Try / catch

try { org = parseOrganization(text, messages, requestId); } catch (e) { if (e.message.includes('不存在的父节点')) retryWithIdList(); else throw e; }

Prevention

When it happens

Trigger: The model references a parent ID that was never emitted as a node, was renamed, or was dropped (e.g. referencing an old node ID from a prior organization run) during parseOrganization.

Common situations: Model invents IDs like "parent-task" that don't exist, refers to nodes from a previous conversation version, or has a typo/case mismatch in an ID.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

function validateOrganization(value,messages,requestId) {
  if(value?.requestId!==requestId||!Array.isArray(value.nodes)||!value.nodes.length)throw Error('整理结果格式不完整');
  const sources=new Set(messages.map(m=>m.id)), nodes=[], ids=new Set();
  for(const n of value.nodes){
    if(!n||typeof n.id!=='string'||!/^[a-zA-Z0-9_-]{1,80}$/.test(n.id)||ids.has(n.id))throw Error('整理结果节点 ID 无效');
    if(!['main','branch'].includes(n.lane)||!Array.isArray(n.sources)||!n.sources.length||!n.sources.every(id=>sources.has(id)))throw Error('整理结果包含无效来源');
    for(const key of ['title','summary','description','status'])if(typeof n[key]!=='string'||!n[key].trim()||n[key].length>12000)throw Error('整理结果缺少节点描述');
    if(n.parent!==null&&typeof n.parent!=='string')throw Error('整理结果父节点无效');
    ids.add(n.id);nodes.push({id:n.id,parent:n.parent,lane:n.lane,title:n.title,summary:n.summary,description:n.description,status:n.status,sources:[...new Set(n.sources)]});
  }
  const roots=nodes.filter(n=>n.parent===null),byId=new Map(nodes.map(n=>[n.id,n]));
  if(roots.length!==1||roots[0].lane!=='main')throw Error('任务树必须有且只有一个主目标根节点');
  const checked=new Set();
  for(const node of nodes){
    const path=new Set();let current=node;
    while(current&&!checked.has(current.id)){
      if(path.has(current.id))throw Error('任务树存在循环关联');
      path.add(current.id);
      if(current.parent!==null&&!byId.has(current.parent))throw Error('任务树包含不存在的父节点');
      current=current.parent===null?null:byId.get(current.parent);
    }
    for(const id of path)checked.add(id);
  }
  const currentNodeId=value.currentNodeId??null;
  if(currentNodeId!==null&&!byId.has(currentNodeId))throw Error('当前推进节点不存在');
  return {schemaVersion:2,currentNodeId,nodes};
}

function parseOrganization(text,messages,requestId) {
  const blocks=[...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map(m=>m[1]);
  for(const raw of [...blocks,text.trim()]){
    let value;try{value=JSON.parse(raw);}catch{continue;}
    if(value?.requestId===requestId)return validateOrganization(value,messages,requestId);
  }
  throw Error('侧边对话未返回可识别的整理结果');
}

View on GitHub (pinned to b1ed92e5e4)