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
- Break the cycle by setting one node's parent to another valid ancestor or null
- Re-run the organize request with instructions that parent links must form a tree, never a cycle
- Verify no node's ancestor chain includes itself
- 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
- Instruct the model that parent links must always point toward the root
- When reparenting nodes in updates, never attach an ancestor under its descendant
- Validate incrementally after each merge
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)