BigPizzaV3/CodexPlusPlus · error · Error

任务树存在循环关联

Error message

任务树存在循环关联

What it means

validateOrganization throws this when following parent links from a node revisits a node already on the current path, i.e. the parent graph contains a cycle. A tree must be acyclic, so circular parent relationships are rejected.

Solutions

  1. Break the cycle by setting one node's parent to another valid ancestor or null
  2. Re-run the organize request with instructions that parent links must form a tree, never a cycle
  3. Verify no node's ancestor chain includes itself
  4. Check nodes that were reparented in an update for accidental loops

Example fix

// before
A.parent=B; B.parent=A  // cycle
// after
A.parent=null (root); B.parent=A
Defensive patterns

Strategy: validation

Validate before calling

function hasCycle(nodes) {
  const byId = new Map(nodes.map(n => [n.id, n]));
  for (const n of nodes) {
    const seen = new Set(); let cur = n;
    while (cur && cur.parent !== null) { if (seen.has(cur.id)) return true; seen.add(cur.id); cur = byId.get(cur.parent); }
  }
  return false;
}

Type guard

function isAcyclicTree(nodes) { return !hasCycle(nodes); }

Try / catch

try { org = parseOrganization(text, messages, requestId); } catch (e) { if (e.message.includes('循环关联')) retryWithAcyclicInstruction(); else throw e; }

Prevention

When it happens

Trigger: Two or more nodes form a parent cycle (A.parent=B, B.parent=A), or a node is its own parent (self-loop), while parseOrganization validates the model output.

Common situations: The model, when merging or reorganizing nodes, reparents an ancestor under its own descendant; batch merges that connect old subtrees into loops.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

}

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)