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

computer-use sidecar returned a non-PNG screenshot payload

Error message

computer-use sidecar returned a non-PNG screenshot payload

What it means

Decoded sidecar payloads must start with the PNG magic signature (89 50 4E 47 0D 0A 1A 0A). Base64-decodable arbitrary bytes are not a valid screenshot, and writing them to the .png destination would turn the sidecar boundary into an arbitrary decoded-byte write, so anything non-PNG (JPEG, WebP, error text) is rejected before tokio::fs::write runs.

Source

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

                        })?;

                    // Decode and validate the PNG payload: it must decode to a
                    // non-empty buffer with a PNG signature. Base64-decodable
                    // arbitrary bytes are NOT a valid screenshot — writing them
                    // to the `.png` destination would turn the sidecar boundary
                    // into an arbitrary decoded-byte write.
                    let png_bytes = base64::engine::general_purpose::STANDARD
                        .decode(png_data)
                        .with_context(|| "Failed to decode PNG base64 data")?;
                    if png_bytes.is_empty() {
                        anyhow::bail!(crate::i18n::get_required_tool_string(
                            "tool-browser-screenshot-error-sidecar-empty-png",
                        ));
                    }
                    const PNG_SIGNATURE: &[u8] =
                        &[0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n'];
                    if !png_bytes.starts_with(PNG_SIGNATURE) {
                        anyhow::bail!(crate::i18n::get_required_tool_string(
                            "tool-browser-screenshot-error-sidecar-not-png",
                        ));
                    }

                    tokio::fs::write(path_str, &png_bytes)
                        .await
                        .with_context(|| format!("Failed to write screenshot to {path_str}"))?;

                    // Return success with the path information
                    let output = serde_json::to_string_pretty(&json!({
                        "backend": "computer_use",
                        "action": action,
                        "path": path_str,
                        "bytes": png_bytes.len(),
                    }))
                    .unwrap_or_default();

                    return Ok(ToolResult {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Configure the sidecar to emit PNG — the contract is PNG-only
  2. Upgrade the sidecar to a version matching the ComputerUseResponse contract
  3. Capture the sidecar's raw response in a debug run to confirm which bytes are actually arriving
Defensive patterns

Strategy: try-catch

Try / catch

match tool.execute(args).await {
    Ok(res) if res.success => { /* file written */ }
    Ok(res) => {
        if res.error.as_deref().unwrap_or_default().contains("non-PNG") {
            // sidecar emitted a non-PNG image; fix its output format config
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The sidecar is configured to return JPEG/WebP screenshots, or stuffs a JSON error blob into png_base64, while the tool call carries a destination path.

Common situations: Screenshot services that default to JPEG output; older sidecar versions with a different response envelope; misrouted sidecar endpoints answering with HTML/text that happens to be base64-encoded.

Related errors


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