zeroclaw-labs/zeroclaw · error
{entry_path}.tool duplicates the rule for tool '{tool}'
Error message
{entry_path}.tool duplicates the rule for tool '{tool}' What it means
The `stream_tool_arguments` list is order-independent: ZeroClaw resolves each tool by exact name, so at most one rule may exist per tool. The validator tracks seen names in a HashSet and rejects a second `{ tool = ... }` entry naming the same tool, because two rules would make the rendered argument set ambiguous.
Source
Thrown at crates/zeroclaw-config/src/schema.rs:15542
anyhow::bail!(
"{entry_path}.default_base duplicates the list's default_base entry"
);
}
saw_defaults = true;
}
StreamToolArgumentEntry::Tool {
tool,
include,
exclude,
..
} => {
if tool.is_empty() || tool.trim() != tool {
anyhow::bail!(
"{entry_path}.tool must be a non-empty exact tool name without surrounding whitespace"
);
}
if !tools.insert(tool.as_str()) {
anyhow::bail!("{entry_path}.tool duplicates the rule for tool '{tool}'");
}
let included =
validate_stream_tool_argument_names(&entry_path, "include", include)?;
let excluded =
validate_stream_tool_argument_names(&entry_path, "exclude", exclude)?;
for field in &excluded {
if included.contains(*field) {
anyhow::bail!(
"{entry_path} includes and excludes the same argument '{field}'"
);
}
}
}
}
}
Ok(())View on GitHub (pinned to 88bb9c8533)
Solutions
- Merge the two rules into a single entry: combine `base`, `include`, and `exclude` into one rule for that tool.
- If one entry is stale, delete it and keep the current one.
- While editing, also keep at most one `default_base` entry — it is subject to the same duplicate rejection.
Example fix
# before
stream_tool_arguments = [
{ tool = "delegate", include = ["agent", "prompt"] },
{ tool = "delegate", exclude = ["token"] },
]
# after
stream_tool_arguments = [
{ tool = "delegate", include = ["agent", "prompt"], exclude = ["token"] },
] Defensive patterns
Strategy: validation
Validate before calling
let mut seen = std::collections::HashSet::new();
for e in &matrix.stream_tool_arguments {
if let StreamToolArgumentEntry::Tool { tool, .. } = e {
if !seen.insert(tool.clone()) {
anyhow::bail!("duplicate rule for tool '{tool}' — merge the entries");
}
}
} Type guard
fn has_unique_tool_rules(cfg: &MatrixConfig) -> bool {
let mut seen = std::collections::HashSet::new();
cfg.stream_tool_arguments.iter().all(|e| match e {
StreamToolArgumentEntry::Tool { tool, .. } => seen.insert(tool.as_str()),
StreamToolArgumentEntry::Defaults { .. } => true,
})
} Prevention
- Keep one rule per tool; edit the existing rule instead of appending a second.
- When layering config files, grep the merged result for duplicated tool keys.
- Treat 'duplicate rule' errors as a prompt to merge semantics, not just delete one.
When it happens
Trigger: Two entries with the same `tool` value in one `stream_tool_arguments` list, e.g. two `{ tool = "delegate", ... }` rules; merging config snippets (base config plus an override file) that each define a rule for the same tool.
Common situations: Appending an override rule for a tool that already has one instead of editing the existing rule; layered configs (site + user) that both tune the same tool; editing a long list and forgetting an earlier entry.
Related errors
- {entry_path}.tool must be a non-empty exact tool name withou
- {entry_path} includes and excludes the same argument '{field
- matrix: {reason} Cannot auto-recover because channels.matrix
- matrix login requires either access_token or user_id+passwor
- matrix: configured channels.matrix.user-id ({configured}) do
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/630d8f4a61164c7c.
Report an issue: GitHub.