{"record":{"id":"a841f150ff305787","repo":"zeroclaw-labs/zeroclaw","slug":"target-node-not-found-to-id","errorCode":null,"errorMessage":"target node not found: {to_id}","messagePattern":"target node not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/knowledge_graph.rs","lineNumber":271,"sourceCode":"    /// Add a directed edge between two nodes.\n    pub fn add_edge(&self, from_id: &str, to_id: &str, relation: Relation) -> anyhow::Result<()> {\n        let conn = self.conn.lock();\n\n        // Verify both endpoints exist.\n        let exists = |id: &str| -> anyhow::Result<bool> {\n            let c: usize = conn.query_row(\n                \"SELECT COUNT(*) FROM nodes WHERE id = ?1\",\n                params![id],\n                |r| r.get(0),\n            )?;\n            Ok(c > 0)\n        };\n\n        if !exists(from_id)? {\n            anyhow::bail!(\"source node not found: {from_id}\");\n        }\n        if !exists(to_id)? {\n            anyhow::bail!(\"target node not found: {to_id}\");\n        }\n\n        conn.execute(\n            \"INSERT OR IGNORE INTO edges (from_id, to_id, relation) VALUES (?1, ?2, ?3)\",\n            params![from_id, to_id, relation.as_str()],\n        )?;\n\n        Ok(())\n    }\n\n    /// Retrieve a node by id.\n    pub fn get_node(&self, id: &str) -> anyhow::Result<Option<KnowledgeNode>> {\n        let conn = self.conn.lock();\n        let mut stmt = conn.prepare(\n            \"SELECT id, node_type, title, content, tags, created_at, updated_at, source_project\n             FROM nodes WHERE id = ?1\",\n        )?;\n","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/knowledge_graph.rs#L253-L289","documentation":"The second half of add_edge's endpoint validation: after the source node check passes, it runs the same SELECT COUNT(*) FROM nodes WHERE id = ? for to_id and bails with this message when the target row is absent. The edges table would enforce this via its FK to nodes(id) anyway; the explicit pre-check yields a precise message naming the missing target id instead of a raw constraint error.","triggerScenarios":"Calling add_edge where to_id does not exist in this graph's nodes table: relating to a deleted/pruned node, a typo'd or truncated UUID, an id from another database file, or a to_id captured from an older run before the graph was recreated.","commonSituations":"Relating new nodes to 'hub' nodes (experts, clients, technologies) that were pruned by capacity or hygiene runs; ids loaded from external config or exports; the same mixed-database mistake as the source variant but surfacing on the target argument.","solutions":["Ensure the target node exists first: create it via add_node or verify with get_node(to_id) and recreate it if it was pruned.","Pass the exact id add_node returned for the target, from the same graph instance/database.","If hub nodes keep disappearing, exclude them from pruning (or re-add them at startup) so relates have stable anchors.","When loading ids from external sources, validate both endpoints before add_edge and report which one is missing."],"exampleFix":"// before\ngraph.add_edge(&pattern_id, \"expert-123\", Relation::AuthoredBy)?; // target node not found: expert-123\n\n// after: look up or create the target node, then relate\nlet expert = graph.query_by_tags(&[\"hub:expert\".into()])?.into_iter().next();\nlet expert_id = match expert { Some(n) => n.id, None => graph.add_node(NodeType::Expert, \"Dana\", \"...\", &[], None)? };\ngraph.add_edge(&pattern_id, &expert_id, Relation::AuthoredBy)?;","handlingStrategy":"validation","validationCode":"// Verify the target before connecting\nif graph.get_node(to_id)?.is_none() {\n    return Err(anyhow::anyhow!(\"cannot relate: target node {to_id} does not exist\"));\n}\ngraph.add_edge(from_id, to_id, relation)?;","typeGuard":"pub fn target_exists(graph: &KnowledgeGraph, to_id: &str) -> bool {\n    graph.get_node(to_id).ok().flatten().is_some()\n}","tryCatchPattern":"// Missing hub targets: recreate the hub, then retry the edge\nmatch graph.add_edge(from_id, to_id, relation) {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"target node not found\") => {\n        let to_id = graph.add_node(NodeType::Expert, to_id, \"recreated hub\", &[], None)?;\n        graph.add_edge(from_id, &to_id, relation)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Exclude hub/anchor nodes (experts, clients, technologies) from pruning and capacity sweeps so relate targets stay stable.","Check get_node(to_id) before add_edge when to_id originated outside the current process.","Log the exact missing id from the error — it distinguishes a pruned node from a wrong-database id.","Keep one graph database path per deployment; mixing paths is the fastest way to produce ids that do not resolve."],"tags":["rust","zeroclaw","knowledge-graph","edges","referential-integrity","validation"],"backgroundTag":"foreign-key-violation","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}