{"record":{"id":"550c1439d363a2ac","repo":"zeroclaw-labs/zeroclaw","slug":"branch-name-contains-invalid-characters","errorCode":null,"errorMessage":"Branch name contains invalid characters","messagePattern":"Branch name contains invalid characters","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/git_operations.rs","lineNumber":600,"sourceCode":"                    .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,\n            }),\n            Err(e) => Ok(ToolResult {\n                success: false,\n                output: ToolOutput::default(),\n                error: Some(format!(\"Checkout failed: {e}\")),\n            }),\n        }","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/git_operations.rs#L582-L618","documentation":"After the single-token shape check passes, git checkout rejects branch names containing '@', '^', or '~' with 'Branch name contains invalid characters'. These characters are the carriers of git revision syntax: '@{' addresses refs like stash@{0} and HEAD@{1}, while '~' and '^' express ancestor ranges like HEAD~2 or main^2. Accepting them would let the 'checkout a branch' operation silently perform reflog jumps or detached-revision checkouts, so the tool restricts checkout to plain branch names. This is a policy restriction of the tool, not a git limitation — the CLI itself accepts those revisions.","triggerScenarios":"Calling checkout with {\"branch\": \"HEAD~1\"}, {\"branch\": \"main^\"}, or {\"branch\": \"stash@{0}\"}. Also any legitimately created branch whose name contains '@' (git allows branch names with '@' as long as they are not exactly '@').","commonSituations":"An agent tries to navigate history and reuses revision syntax in the branch field; a caller wants to check out a stashed state via stash@{0}; a branch was created with an email-like name (user@feature) and now cannot be checked out through this tool; porting a shell script that used HEAD~1 directly.","solutions":["Pass a plain branch name that exists locally, e.g. {\"branch\": \"main\"} or {\"branch\": \"feature-x\"}.","For detached-HEAD/history navigation, create or check out a branch at the target revision using the CLI or another tool surface — this tool intentionally will not take HEAD~1.","If your branch genuinely contains '@', rename it (git branch -m) to a dash-separated name before using this checkout tool.","Use git_branch-style operations to list valid branch names first and pick from those."],"exampleFix":"// before\nlet args = serde_json::json!({ \"branch\": \"HEAD~1\" }); // revision syntax, not a branch name\n// tool bails: Branch name contains invalid characters\n\n// after\nlet args = serde_json::json!({ \"branch\": \"main\" });","handlingStrategy":"type-guard","validationCode":"fn build_checkout_args(branch: &str) -> Option<serde_json::Value> {\n    is_plain_branch_name(branch).then(|| serde_json::json!({ \"branch\": branch }))\n}","typeGuard":"fn is_plain_branch_name(branch: &str) -> bool {\n    !branch.contains('@') && !branch.contains('^') && !branch.contains('~')\n}","tryCatchPattern":"match tool_result {\n    Err(e) if e.to_string().contains(\"Branch name contains invalid characters\") => {\n        // revision syntax was passed; resolve it to a concrete branch name first\n    }\n    other => other,\n}","preventionTips":["Never feed revision expressions (HEAD~n, ref@{n}, x^y) into a checkout-branch API; resolve them to branch names first.","Validate branch names against the tool's denylist ('@', '^', '~') in your own schema layer for earlier, clearer errors.","Rename legacy branches containing '@' at creation time rather than working around checkout later."],"tags":["git","checkout","branch","security","input-sanitization","zeroclaw-tools"],"backgroundTag":"invalid-characters-in-input","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}