zeroclaw-labs/zeroclaw · error

subgraph depth must be greater than 0

Error message

subgraph depth must be greater than 0

What it means

get_subgraph(root_id, depth) walks the graph outward from a root via a recursive CTE and treats depth as 'how many hops to include', so 0 would select nothing. It validates depth > 0 up front and bails with this message; after validation the depth is internally clamped to MAX_SUBGRAPH_DEPTH (100), so 0 is the only invalid value — anything from 1 upward is accepted.

Source

Thrown at crates/zeroclaw-memory/src/knowledge_graph.rs:549

        while let Some(row) = rows.next()? {
            results.push(row_to_node(row)?);
        }
        Ok(results)
    }

    /// Maximum allowed subgraph traversal depth.
    const MAX_SUBGRAPH_DEPTH: usize = 100;

    /// Extract a subgraph starting from `root_id` up to `depth` hops.
    /// `depth` must be between 1 and `MAX_SUBGRAPH_DEPTH` (100).
    /// Uses a recursive CTE for efficient single-query bidirectional traversal.
    pub fn get_subgraph(
        &self,
        root_id: &str,
        depth: usize,
    ) -> anyhow::Result<(Vec<KnowledgeNode>, Vec<KnowledgeEdge>)> {
        if depth == 0 {
            anyhow::bail!("subgraph depth must be greater than 0");
        }
        let depth = depth.min(Self::MAX_SUBGRAPH_DEPTH);
        let conn = self.conn.lock();

        // Collect reachable node IDs via recursive CTE (bidirectional traversal).
        let mut node_stmt = conn.prepare(
            "WITH RECURSIVE reachable(id, depth) AS (
                SELECT ?1, 0
                UNION
                SELECT CASE WHEN e.from_id = r.id THEN e.to_id ELSE e.from_id END, r.depth + 1
                FROM reachable r
                JOIN edges e ON e.from_id = r.id OR e.to_id = r.id
                WHERE r.depth < ?2
             )
             SELECT DISTINCT n.id, n.node_type, n.title, n.content, n.tags,
                    n.created_at, n.updated_at, n.source_project
             FROM reachable rc
             JOIN nodes n ON n.id = rc.id",

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pass at least 1: depth 1 means 'the root plus its direct neighbors'.
  2. If the value is computed, clamp it before the call: let depth = depth.clamp(1, KnowledgeGraph::MAX_SUBGRAPH_DEPTH).
  3. If 0 was meant as 'no limit', pass 100 (MAX_SUBGRAPH_DEPTH) instead — the implementation clamps to exactly that.
  4. Guard loops that decrement depth so they stop at 1 rather than calling with 0.

Example fix

// before
let (nodes, edges) = graph.get_subgraph(&root, remaining_depth)?; // remaining_depth == 0 -> bail

// after
let depth = remaining_depth.max(1); // 0 hops is meaningless; 1 = root + direct neighbors
let (nodes, edges) = graph.get_subgraph(&root, depth)?;
Defensive patterns

Strategy: validation

Validate before calling

// Clamp computed depths before the call
let depth = depth.clamp(1, 100); // 1..=KnowledgeGraph::MAX_SUBGRAPH_DEPTH
let (nodes, edges) = graph.get_subgraph(&root_id, depth)?;

Type guard

pub fn valid_subgraph_depth(depth: usize) -> bool {
    depth >= 1 // values above 100 are clamped internally, only 0 is invalid
}

Try / catch

// Expansion loops: treat 0 as 'direct neighbors only'
let depth = if remaining == 0 { 1 } else { remaining };
match graph.get_subgraph(&root_id, depth) {
    Ok(sub) => render(sub),
    Err(e) if e.to_string().contains("depth must be greater than 0") => {
        render(graph.get_subgraph(&root_id, 1)?)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_subgraph with depth == 0, most often because a caller-computed depth (levels minus one, a remaining-depth variable in a loop, or a config default of 0) underflowed or was never set. Hit directly or via client-facing wrappers that forward a user-supplied depth (client_relationship_types_roundtrip_through_queries tests exercise the same path).

Common situations: UI code mapping 'current level' to hops as level-1 and rendering level 0; pagination/expansion loops that reach 0 remaining hops and call instead of stopping; configs defaulting depth to 0 meaning 'unlimited' (here unlimited is expressed by any value >= 100 via the clamp, not 0).

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/f24e65add726fb49. Report an issue: GitHub.