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
- Configure the sidecar to emit PNG — the contract is PNG-only
- Upgrade the sidecar to a version matching the ComputerUseResponse contract
- 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
- Verify the sidecar emits PNG (not JPEG/WebP) in its configuration
- Contract-test the sidecar response shape (data.png_base64) in CI
- Do not write sidecar bytes to disk yourself — let the tool validate the signature
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
- computer-use sidecar returned an empty screenshot payload
- Screenshot 'path' parameter must be a string, got { $path }
- Cannot write screenshot to runtime config path '{ $target }'
- Cannot write screenshot to symlink target '{ $target }'
- Screenshot path '{ $path }' resolves to a non-UTF-8 pathname
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3a804aadad1968f4.
Report an issue: GitHub.