zeroclaw-labs/zeroclaw · error · anyhow::Error

Screenshot 'path' parameter must be a string, got { $path }

Error message

Screenshot 'path' parameter must be a string, got { $path }

What it means

In the computer-use flow, the screenshot `path` argument is classified as Absent, String, or NonString. Null, missing, or empty-string means 'return the PNG inline'; a string is validated as a write target; every other JSON type (integer, array, object, boolean) hits the NonString arm and is rejected with the debug-formatted value. The strictness exists because the validated value is later used for a local filesystem write.

Source

Thrown at crates/zeroclaw-tools/src/browser.rs:1030

                // String: validate against workspace through the one canonical
                // validator shared with the local backends.
                let mut args = args;
                let resolved_target = self.validate_screenshot_target(path_str).await?;

                // Store the validated path for local write after sidecar returns PNG.
                // Do NOT forward the path to the sidecar - it returns PNG bytes.
                if let Some(obj) = args.as_object_mut() {
                    obj.insert("path".to_string(), Value::String(resolved_target));
                }
                Ok(args)
            }
            Some(_) => {
                // NonString: integer, array, object → reject
                let msg = crate::i18n::get_required_tool_string_with_args(
                    "tool-browser-screenshot-error-computeruse-non-string-path",
                    &[("path", &format!("{path:?}"))],
                );
                anyhow::bail!("{msg}");
            }
        }
    }

    async fn execute_computer_use_action(
        &self,
        action: &str,
        args: &Value,
    ) -> anyhow::Result<ToolResult> {
        let endpoint = self.computer_use_endpoint_url()?;

        // Validate screenshot path but do NOT forward it to the sidecar.
        // The sidecar returns PNG bytes, and we perform the validated local write.
        let validated_path = if action == "screenshot" {
            match self
                .validate_screenshot_path_for_computer_use(action, args.clone())
                .await
            {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pass path as a JSON string ("screenshots/shot.png") or omit it entirely to get the PNG inline
  2. Enforce the tool's parameters schema (type: string) before dispatch
  3. Treat empty string as 'inline PNG' if no file write is wanted

Example fix

// before
{"action": "screenshot", "path": 1}
// after
{"action": "screenshot", "path": "screenshots/shot.png"}
Defensive patterns

Strategy: type-guard

Validate before calling

if !matches!(args.get("path"), None | Some(serde_json::Value::Null) | Some(serde_json::Value::String(_))) {
    return Err("screenshot 'path' must be a string, null, or absent".into());
}

Type guard

fn is_valid_screenshot_path_param(args: &serde_json::Value) -> bool {
    match args.get("path") {
        None | Some(serde_json::Value::Null) => true,
        Some(serde_json::Value::String(_)) => true,
        _ => false,
    }
}

Try / catch

match tool.execute(args).await {
    Ok(res) if res.success => { /* ... */ }
    Ok(res) => { /* res.error carries the rejection; fix the arg type, not the environment */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Sending {"action": "screenshot", "path": 0}, {"path": ["shot.png"]}, or {"path": {"file": "shot.png"}} to the browser tool with computer-use configured.

Common situations: An LLM emits a numeric flag or a structured object where a path string was expected; upstream code forwards unvalidated JSON; a model confuses the numeric `pixels` parameter with `path`.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/66aa1169b69d913d. Report an issue: GitHub.