zeroclaw-labs/zeroclaw · error
tag '{}' contains a comma, which is used as the tag separato
Error message
tag '{}' contains a comma, which is used as the tag separator What it means
KnowledgeGraph stores a node's tags as a single comma-joined TEXT column (tags.join(",") at insert; split back on ',') and mirrors that string into the FTS index, so the comma is the reserved separator. add_node rejects any tag containing a comma before writing, keeping stored tags unambiguously splittable; without this check a tag like "rust, memory" would silently become two tags on read-back.
Source
Thrown at crates/zeroclaw-memory/src/knowledge_graph.rs:224
tags: &[String],
source_project: Option<&str>,
) -> anyhow::Result<String> {
let conn = self.conn.lock();
// Enforce max_nodes limit.
let count: usize = conn.query_row("SELECT COUNT(*) FROM nodes", [], |r| r.get(0))?;
if count >= self.max_nodes {
anyhow::bail!(
"knowledge graph node limit reached ({}/{})",
count,
self.max_nodes
);
}
// Reject tags containing commas since comma is the separator in storage.
for tag in tags {
if tag.contains(',') {
anyhow::bail!(
"tag '{}' contains a comma, which is used as the tag separator",
tag
);
}
}
let id = Uuid::new_v4().to_string();
let now = Utc::now().to_rfc3339();
let tags_str = tags.join(",");
conn.execute(
"INSERT INTO nodes (id, node_type, title, content, tags, created_at, updated_at, source_project)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![
id,
node_type.as_str(),
title,
content,View on GitHub (pinned to 88bb9c8533)
Solutions
- Split comma-separated input into separate tags before calling add_node: "a, b".split(',').map(str::trim).filter(|s| !s.is_empty()).
- If a comma is genuinely part of the tag's meaning, replace it with another separator (e.g. '-' or '_') since the storage format cannot encode it.
- Validate/sanitize tags at the boundary where user or LLM text enters the system, not at the graph call site only.
- Keep the failing tag from the message — it names exactly which element to fix.
Example fix
// before
let tags = vec!["rust, memory".to_string()];
graph.add_node(NodeType::Pattern, "t", "c", &tags, None)?; // tag contains a comma
// after
let tags: Vec<String> = "rust, memory"
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect(); // ["rust", "memory"]
graph.add_node(NodeType::Pattern, "t", "c", &tags, None)?; Defensive patterns
Strategy: validation
Validate before calling
// Sanitize tags at the boundary
fn clean_tags(raw: &str) -> Vec<String> {
raw.split(',')
.map(str::trim)
.filter(|s| !s.is_empty() && !s.contains(','))
.map(str::to_string)
.collect()
}
let tags = clean_tags(&user_tags);
assert!(tags.iter().all(|t| !t.contains(',')));
graph.add_node(NodeType::Lesson, title, content, &tags, None)?; Type guard
pub fn tags_are_graph_safe(tags: &[String]) -> bool {
tags.iter().all(|t| !t.is_empty() && !t.contains(','))
} Try / catch
// Repair per-tag instead of failing the whole capture
for tag in tags.iter().filter(|t| t.contains(',')) {
tracing::warn!(tag, "tag contains reserved separator ','; splitting");
}
let tags: Vec<String> = tags.iter().flat_map(|t| t.split(',')).map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect();
graph.add_node(node_type, title, content, &tags, None)?; Prevention
- Treat the comma as reserved: split CSV-ish user input into a Vec<String> of single tags before add_node.
- Sanitize at the text-ingestion boundary (user input, LLM output), not only at the graph call site.
- Include tags_are_graph_safe in test fixtures for any code path that constructs tags dynamically.
- If a tag genuinely needs an internal separator, choose '-' or '_' — the storage format cannot encode commas.
When it happens
Trigger: Calling add_node (or a capture path feeding it) with a tags slice where any element contains ',', e.g. tags = &["rust, memory".to_string()] or user-supplied tag strings pasted from a comma-separated list without splitting.
Common situations: Accepting user-provided tags verbatim ("tags: a, b, c" typed as one string); forwarding CSV data or LLM-extracted tag lists straight into add_node; test fixtures that join tags themselves before passing them.
Related errors
- unknown {}: {other}
- knowledge graph node limit reached ({}/{})
- source node not found: {from_id}
- target node not found: {to_id}
- subgraph depth must be greater than 0
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/732daea93af8c90c.
Report an issue: GitHub.