{"record":{"id":"700530007c74d5e3","repo":"zeroclaw-labs/zeroclaw","slug":"no-paths-to-stage","errorCode":null,"errorMessage":"No paths to stage","messagePattern":"No paths to stage","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/git_operations.rs","lineNumber":551,"sourceCode":"        working_dir: &std::path::Path,\n    ) -> anyhow::Result<ToolResult> {\n        let paths = args.get(\"paths\").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\": \"paths\"})),\n                \"git_operations: missing paths parameter\"\n            );\n            anyhow::Error::msg(\"Missing 'paths' parameter\")\n        })?;\n\n        // Validate paths against injection patterns. Returns each\n        // whitespace-separated pathspec as its own argument so the join is\n        // not handed to git as a single literal path.\n        let sanitized = self.sanitize_git_args(paths)?;\n        if sanitized.is_empty() {\n            anyhow::bail!(\"No paths to stage\");\n        }\n\n        let mut git_args: Vec<&str> = vec![\"add\", \"--\"];\n        git_args.extend(sanitized.iter().map(String::as_str));\n\n        let output = self.run_git_command(&git_args, working_dir).await;\n\n        match output {\n            Ok(_) => Ok(ToolResult {\n                success: true,\n                output: format!(\"Staged: {}\", sanitized.join(\" \")).into(),\n                error: None,\n            }),\n            Err(e) => Ok(ToolResult {\n                success: false,\n                output: ToolOutput::default(),\n                error: Some(format!(\"Add failed: {e}\")),\n            }),","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/git_operations.rs#L533-L569","documentation":"The git add tool requires a 'paths' string and runs it through sanitize_git_args, which both rejects injection patterns and splits the whitespace-separated pathspec string into individual arguments. If sanitization yields zero tokens (sanitized.is_empty()), the tool bails with 'No paths to stage' before running `git add`. The guard prevents an argument-less `git add` invocation, which would either error or, depending on flags, stage nothing meaningful. A missing 'paths' key is a separate error ('Missing 'paths' parameter'); this one means the key existed but contained no usable pathspec tokens.","triggerScenarios":"Calling git add with {\"paths\": \"\"} or {\"paths\": \"   \"} — a string with no whitespace-separated tokens. Also occurs when a caller forwards a joined list that turned out empty, e.g. format!(\"{}\", changed_files.join(\" \")) on an empty vec.","commonSituations":"A pipeline computes changed files programmatically and passes an empty join when nothing changed; an agent calls add before any edits exist; a wrapper defaults paths to an empty string instead of \".\"; a path list variable is unset in config.","solutions":["Pass at least one real pathspec, e.g. {\"paths\": \"src/main.rs\"} or a directory like {\"paths\": \"crates/zeroclaw-tools\"}.","To stage everything in the working tree, pass \".\" explicitly: {\"paths\": \".\"} — the tool appends it after `add --` so it is treated as a literal path.","If paths come from a computed list, skip the add call entirely when the list is empty rather than invoking the tool.","Check for accidental whitespace-only strings from templating or env substitution before calling."],"exampleFix":"// before\nlet args = serde_json::json!({ \"paths\": \"\" });\n// tool bails: No paths to stage\n\n// after\nlet args = serde_json::json!({ \"paths\": \".\" }); // stage all changes","handlingStrategy":"validation","validationCode":"fn build_add_args(paths: &[String]) -> Option<serde_json::Value> {\n    (!paths.is_empty()).then(|| serde_json::json!({ \"paths\": paths.join(\" \") }))\n}","typeGuard":null,"tryCatchPattern":"match tool_result {\n    Err(e) if e.to_string().contains(\"No paths to stage\") => {\n        // nothing to stage: treat as a no-op success, not a failure\n    }\n    other => other,\n}","preventionTips":["Skip the add call when the computed changed-file list is empty instead of joining an empty vec.","Default 'paths' to \".\" when the intent is 'stage everything'.","Split pathspecs on whitespace yourself first to confirm at least one token survives."],"tags":["git","staging","validation","zeroclaw-tools","agent-tools"],"backgroundTag":"missing-required-parameter","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}