gitbutlerapp/gitbutler · error · anyhow::Error
Tool '{name}' not found
Error message
Tool '{name}' not found What it means
WorkspaceToolset::call_tool_inner looked up the requested tool name in its BTreeMap of registered tools and found nothing. The toolset only contains tools explicitly registered via register_tool, so the caller (usually an LLM tool-call loop or SDK consumer) used a name that was never registered on this instance.
Source
Thrown at crates/but-tools/src/tool.rs:41
}
impl<'a> WorkspaceToolset<'a> {
pub fn new(ctx: &'a mut Context) -> Self {
WorkspaceToolset {
ctx,
tools: BTreeMap::new(),
commit_mapping: HashMap::new(),
}
}
fn call_tool_inner(
&mut self,
name: &str,
parameters: &str,
) -> anyhow::Result<serde_json::Value> {
let tool = self
.get(name)
.ok_or_else(|| anyhow::anyhow!("Tool '{name}' not found"))?;
let params: serde_json::Value = serde_json::from_str(parameters)
.map_err(|e| anyhow::anyhow!("Failed to parse parameters: {e}"))?;
tool.call(params, self.ctx, &mut self.commit_mapping)
}
}
impl Toolset for WorkspaceToolset<'_> {
fn register_tool<T: Tool>(&mut self, tool: T) {
self.tools.insert(tool.name(), Arc::new(tool));
}
fn get(&self, name: &str) -> Option<Arc<dyn Tool>> {
self.tools.get(name).cloned()
}
fn list(&self) -> Vec<Arc<dyn Tool>> {
self.tools.values().cloned().collect()
}View on GitHub (pinned to caf1f223d3)
Solutions
- Call toolset.list() and use only returned tool names when issuing calls
- Register the missing tool on the WorkspaceToolset before calling it
- Check for renames between versions of but-tools if the name worked before
Example fix
// before
toolset.call_tool("list_branches", "{}");
// after — discover registered names first
let names: Vec<String> = toolset.list().iter().map(|t| t.name()).collect();
assert!(names.contains(&"list_branches".to_string()), "available: {names:?}");
toolset.call_tool("list_branches", "{}"); Defensive patterns
Strategy: validation
Validate before calling
let available: std::collections::HashSet<String> =
toolset.list().iter().map(|t| t.name()).collect();
if !available.contains(requested_name) {
return Err(anyhow!("tool '{requested_name}' not registered; available: {available:?}"));
} Type guard
fn is_registered(toolset: &WorkspaceToolset<'_>, name: &str) -> bool {
toolset.get(name).is_some()
} Try / catch
match toolset.call_tool_inner(name, params) {
Ok(v) => v,
Err(e) if e.to_string().contains("Tool '") && e.to_string().contains("not found") => {
// restrict LLM tool list to toolset.list() output and re-prompt
json!({"error": e.to_string(), "available": toolset.list().iter().map(|t| t.name()).collect::<Vec<_>>()})
}
Err(e) => return json!({"error": e.to_string()}),
} Prevention
- Always advertise tools to models from toolset.list(), never from a hardcoded list
- Register all tools immediately after WorkspaceToolset::new
- Return the available-tool list in error payloads so callers self-correct
When it happens
Trigger: Calling toolset.call_tool("get_workspace", ...) on a WorkspaceToolset built without registering that tool; an LLM hallucinating a tool name not in the registered set; version skew where a tool was renamed or not yet registered.
Common situations: Agent/LLM integration invokes a tool from a different toolset version; a consumer copies a tool name from docs but forgets to call register_tool for it first.
Related errors
- Failed to parse parameters: {e}
- Failed to communicate with LM Studio server: ${error instanc
- Invalid response: ${JSON.stringify(result)}
- When using GitButler's API to summarize code, you must be lo
- When using LM Studio, you must provide a valid endpoint
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/22f72cd50d9b8ccd.
Report an issue: GitHub.