zeroclaw-labs/zeroclaw · error
target node not found: {to_id}
Error message
target node not found: {to_id} What it means
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.
Source
Thrown at crates/zeroclaw-memory/src/knowledge_graph.rs:271
/// Add a directed edge between two nodes.
pub fn add_edge(&self, from_id: &str, to_id: &str, relation: Relation) -> anyhow::Result<()> {
let conn = self.conn.lock();
// Verify both endpoints exist.
let exists = |id: &str| -> anyhow::Result<bool> {
let c: usize = conn.query_row(
"SELECT COUNT(*) FROM nodes WHERE id = ?1",
params![id],
|r| r.get(0),
)?;
Ok(c > 0)
};
if !exists(from_id)? {
anyhow::bail!("source node not found: {from_id}");
}
if !exists(to_id)? {
anyhow::bail!("target node not found: {to_id}");
}
conn.execute(
"INSERT OR IGNORE INTO edges (from_id, to_id, relation) VALUES (?1, ?2, ?3)",
params![from_id, to_id, relation.as_str()],
)?;
Ok(())
}
/// Retrieve a node by id.
pub fn get_node(&self, id: &str) -> anyhow::Result<Option<KnowledgeNode>> {
let conn = self.conn.lock();
let mut stmt = conn.prepare(
"SELECT id, node_type, title, content, tags, created_at, updated_at, source_project
FROM nodes WHERE id = ?1",
)?;
View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before
graph.add_edge(&pattern_id, "expert-123", Relation::AuthoredBy)?; // target node not found: expert-123
// after: look up or create the target node, then relate
let expert = graph.query_by_tags(&["hub:expert".into()])?.into_iter().next();
let expert_id = match expert { Some(n) => n.id, None => graph.add_node(NodeType::Expert, "Dana", "...", &[], None)? };
graph.add_edge(&pattern_id, &expert_id, Relation::AuthoredBy)?; Defensive patterns
Strategy: validation
Validate before calling
// Verify the target before connecting
if graph.get_node(to_id)?.is_none() {
return Err(anyhow::anyhow!("cannot relate: target node {to_id} does not exist"));
}
graph.add_edge(from_id, to_id, relation)?; Type guard
pub fn target_exists(graph: &KnowledgeGraph, to_id: &str) -> bool {
graph.get_node(to_id).ok().flatten().is_some()
} Try / catch
// Missing hub targets: recreate the hub, then retry the edge
match graph.add_edge(from_id, to_id, relation) {
Ok(()) => {}
Err(e) if e.to_string().contains("target node not found") => {
let to_id = graph.add_node(NodeType::Expert, to_id, "recreated hub", &[], None)?;
graph.add_edge(from_id, &to_id, relation)?;
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- source node not found: {from_id}
- unknown {}: {other}
- cloud_ops.iac_tools must not be empty when cloud_ops is enab
- gateway.path_prefix contains invalid character '{bad}'; only
- risk_profiles.{profile_alias}.shell_env_passthrough[{i}] is
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/a841f150ff305787.
Report an issue: GitHub.