{"record":{"id":"ee656e65ac189cd0","repo":"BigPizzaV3/CodexPlusPlus","slug":"canvas-user-ee656e","errorCode":null,"errorMessage":"任务树包含不存在的父节点","messagePattern":"任务树包含不存在的父节点","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/public/canvas.user.js","lineNumber":133,"sourceCode":"function validateOrganization(value,messages,requestId) {\n  if(value?.requestId!==requestId||!Array.isArray(value.nodes)||!value.nodes.length)throw Error('整理结果格式不完整');\n  const sources=new Set(messages.map(m=>m.id)), nodes=[], ids=new Set();\n  for(const n of value.nodes){\n    if(!n||typeof n.id!=='string'||!/^[a-zA-Z0-9_-]{1,80}$/.test(n.id)||ids.has(n.id))throw Error('整理结果节点 ID 无效');\n    if(!['main','branch'].includes(n.lane)||!Array.isArray(n.sources)||!n.sources.length||!n.sources.every(id=>sources.has(id)))throw Error('整理结果包含无效来源');\n    for(const key of ['title','summary','description','status'])if(typeof n[key]!=='string'||!n[key].trim()||n[key].length>12000)throw Error('整理结果缺少节点描述');\n    if(n.parent!==null&&typeof n.parent!=='string')throw Error('整理结果父节点无效');\n    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)]});\n  }\n  const roots=nodes.filter(n=>n.parent===null),byId=new Map(nodes.map(n=>[n.id,n]));\n  if(roots.length!==1||roots[0].lane!=='main')throw Error('任务树必须有且只有一个主目标根节点');\n  const checked=new Set();\n  for(const node of nodes){\n    const path=new Set();let current=node;\n    while(current&&!checked.has(current.id)){\n      if(path.has(current.id))throw Error('任务树存在循环关联');\n      path.add(current.id);\n      if(current.parent!==null&&!byId.has(current.parent))throw Error('任务树包含不存在的父节点');\n      current=current.parent===null?null:byId.get(current.parent);\n    }\n    for(const id of path)checked.add(id);\n  }\n  const currentNodeId=value.currentNodeId??null;\n  if(currentNodeId!==null&&!byId.has(currentNodeId))throw Error('当前推进节点不存在');\n  return {schemaVersion:2,currentNodeId,nodes};\n}\n\nfunction parseOrganization(text,messages,requestId) {\n  const blocks=[...text.matchAll(/```(?:json)?\\s*([\\s\\S]*?)```/g)].map(m=>m[1]);\n  for(const raw of [...blocks,text.trim()]){\n    let value;try{value=JSON.parse(raw);}catch{continue;}\n    if(value?.requestId===requestId)return validateOrganization(value,messages,requestId);\n  }\n  throw Error('侧边对话未返回可识别的整理结果');\n}\n","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/public/canvas.user.js#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure every non-null parent string equals the id of a node in the same result","Remove or re-point dangling parent references to existing node IDs","Re-run the organize request instructing the model to only reference IDs it defines","Check for typos or stale IDs from previous runs"],"exampleFix":"// before\n{\"id\":\"b\",\"parent\":\"old-root-1\"}  // no such node\n// after\n{\"id\":\"b\",\"parent\":\"root\"}  // existing node id","handlingStrategy":"validation","validationCode":"const byId = new Set(nodes.map(n => n.id));\nfor (const n of nodes) if (n.parent !== null && !byId.has(n.parent)) console.warn('dangling parent', n.id, '->', n.parent);","typeGuard":"function parentsExist(nodes) {\n  const ids = new Set(nodes.map(n => n.id));\n  return nodes.every(n => n.parent === null || ids.has(n.parent));\n}","tryCatchPattern":"try { org = parseOrganization(text, messages, requestId); } catch (e) { if (e.message.includes('不存在的父节点')) retryWithIdList(); else throw e; }","preventionTips":["Provide the full list of valid node IDs to the model in each request","Reject drafts that reference IDs not in the current node catalog","Check for typos/stale IDs from earlier runs before submitting"],"tags":["validation","tree-structure","reference-integrity"],"backgroundTag":"record-not-found","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}