{"record":{"id":"0b10ded96f167bab","repo":"zeroclaw-labs/zeroclaw","slug":"invalid-branch-specification","errorCode":null,"errorMessage":"Invalid branch specification","messagePattern":"Invalid branch specification","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/git_operations.rs","lineNumber":593,"sourceCode":"        args: serde_json::Value,\n        working_dir: &std::path::Path,\n    ) -> anyhow::Result<ToolResult> {\n        let branch = args.get(\"branch\").and_then(|v| v.as_str()).ok_or_else(|| {\n            ::zeroclaw_log::record!(\n                WARN,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(::serde_json::json!({\"param\": \"branch\"})),\n                \"git_operations: missing branch parameter\"\n            );\n            anyhow::Error::msg(\"Missing 'branch' parameter\")\n        })?;\n\n        // Sanitize branch name\n        let sanitized = self.sanitize_git_args(branch)?;\n\n        if sanitized.is_empty() || sanitized.len() > 1 {\n            anyhow::bail!(\"Invalid branch specification\");\n        }\n\n        let branch_name = &sanitized[0];\n\n        // Block dangerous branch names\n        if branch_name.contains('@') || branch_name.contains('^') || branch_name.contains('~') {\n            anyhow::bail!(\"Branch name contains invalid characters\");\n        }\n\n        let output = self\n            .run_git_command(&[\"checkout\", branch_name], working_dir)\n            .await;\n\n        match output {\n            Ok(_) => Ok(ToolResult {\n                success: true,\n                output: format!(\"Switched to branch: {branch_name}\").into(),\n                error: None,","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/git_operations.rs#L575-L611","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet args = serde_json::json!({ \"branch\": \"feature login refactor\" }); // 3 tokens after sanitize\n// tool bails: Invalid branch specification\n\n// after\nlet args = serde_json::json!({ \"branch\": \"feature-login-refactor\" });","handlingStrategy":"validation","validationCode":"fn build_checkout_args(branch: &str) -> Option<serde_json::Value> {\n    let tokens: Vec<&str> = branch.split_whitespace().collect();\n    (tokens.len() == 1).then(|| serde_json::json!({ \"branch\": tokens[0] }))\n}","typeGuard":"fn is_single_token_spec(branch: &str) -> bool {\n    let mut tokens = branch.split_whitespace();\n    tokens.next().is_some() && tokens.next().is_none()\n}","tryCatchPattern":"match tool_result {\n    Err(e) if e.to_string().contains(\"Invalid branch specification\") => {\n        // the value had zero or 2+ whitespace tokens; log it and fall back to a known branch\n    }\n    other => other,\n}","preventionTips":["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."],"tags":["git","checkout","branch","validation","zeroclaw-tools"],"backgroundTag":"invalid-parameter-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}