zeroclaw-labs/zeroclaw · error · anyhow::Error
Invalid branch specification
Error message
Invalid branch specification
What it means
The git checkout tool takes a 'branch' string, sanitizes it into whitespace-separated tokens, and requires exactly one non-empty token. Zero tokens (blank branch) or more than one token (any internal whitespace) bails with 'Invalid branch specification'. The single-token rule exists because the sanitized result is used as the sole branch argument to `git checkout`, so a multi-token string would either be ambiguous or interpreted as extra arguments. This is a shape check on the parameter, distinct from the character-level check that follows it.
Source
Thrown at crates/zeroclaw-tools/src/git_operations.rs:593
args: serde_json::Value,
working_dir: &std::path::Path,
) -> anyhow::Result<ToolResult> {
let branch = args.get("branch").and_then(|v| v.as_str()).ok_or_else(|| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({"param": "branch"})),
"git_operations: missing branch parameter"
);
anyhow::Error::msg("Missing 'branch' parameter")
})?;
// Sanitize branch name
let sanitized = self.sanitize_git_args(branch)?;
if sanitized.is_empty() || sanitized.len() > 1 {
anyhow::bail!("Invalid branch specification");
}
let branch_name = &sanitized[0];
// Block dangerous branch names
if branch_name.contains('@') || branch_name.contains('^') || branch_name.contains('~') {
anyhow::bail!("Branch name contains invalid characters");
}
let output = self
.run_git_command(&["checkout", branch_name], working_dir)
.await;
match output {
Ok(_) => Ok(ToolResult {
success: true,
output: format!("Switched to branch: {branch_name}").into(),
error: None,View on GitHub (pinned to 88bb9c8533)
Solutions
- Pass exactly one whitespace-free branch name, e.g. {"branch": "feature-login"}.
- Use hyphens or underscores in branch names instead of spaces when you control branch creation.
- Default the branch in the caller (e.g. to the repo's main branch) when the configured value is blank, instead of forwarding an empty string.
- Trim the value and verify it has no internal whitespace before invoking the tool.
Example fix
// before
let args = serde_json::json!({ "branch": "feature login refactor" }); // 3 tokens after sanitize
// tool bails: Invalid branch specification
// after
let args = serde_json::json!({ "branch": "feature-login-refactor" }); Defensive patterns
Strategy: validation
Validate before calling
fn build_checkout_args(branch: &str) -> Option<serde_json::Value> {
let tokens: Vec<&str> = branch.split_whitespace().collect();
(tokens.len() == 1).then(|| serde_json::json!({ "branch": tokens[0] }))
} Type guard
fn is_single_token_spec(branch: &str) -> bool {
let mut tokens = branch.split_whitespace();
tokens.next().is_some() && tokens.next().is_none()
} Try / catch
match tool_result {
Err(e) if e.to_string().contains("Invalid branch specification") => {
// the value had zero or 2+ whitespace tokens; log it and fall back to a known branch
}
other => other,
} Prevention
- Enforce a branch-name charset (alnum, '-', '_', '/', '.') at branch-creation time so spaces never reach checkout.
- Source branch names from git_branch list output or your own creation API, never free-form text fields.
- Trim user input and reject internal whitespace before constructing the args JSON.
When it happens
Trigger: Calling checkout with {"branch": ""} or {"branch": " "} (zero tokens), or with {"branch": "feature login refactor"} / {"branch": "origin main"} (whitespace splits into 2+ tokens). Any branch value containing a space, tab, or newline triggers it.
Common situations: A caller interpolates a human-readable description into the branch field; an agent passes a git ref expression that contains a space; a config value for the default branch is empty; copy-pasting a branch name with a trailing comment or space-padded padding from logs.
Related errors
- Branch name contains invalid characters
- Commit message cannot be empty
- No paths to stage
- Unknown stash action: {action}. Use: push, pop, list, drop
- Missing 'subcommand' parameter. Use: list, add, remove, prun
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0b10ded96f167bab.
Report an issue: GitHub.