BigPizzaV3/CodexPlusPlus · error · anyhow::Error

Page.captureScreenshot returned no image data

Error message

Page.captureScreenshot returned no image data

What it means

capture_screenshot (crates/codex-plus-core/src/bridge.rs:130) sends the CDP command Page.captureScreenshot over the debug websocket and expects response.result.data to be a non-empty base64 string. If the field is missing, null, not a string, or an empty string, this error is raised before the base64/PNG decoding stages. It means the browser answered but attached no screenshot payload — typically an empty/error-shaped CDP response rather than a transport failure.

Source

Thrown at crates/codex-plus-core/src/bridge.rs:130

        .await
}

pub async fn capture_page_screenshot(
    websocket_url: &str,
    output_path: &Path,
) -> anyhow::Result<u64> {
    let response = send_cdp_command(
        websocket_url,
        "Page.captureScreenshot",
        capture_screenshot_params(),
    )
    .await?;
    let encoded = response
        .get("result")
        .and_then(|result| result.get("data"))
        .and_then(Value::as_str)
        .filter(|data| !data.is_empty())
        .ok_or_else(|| anyhow::anyhow!("Page.captureScreenshot returned no image data"))?;
    let bytes = base64::engine::general_purpose::STANDARD
        .decode(encoded)
        .context("failed to decode screenshot PNG")?;
    if !bytes.starts_with(&[137, 80, 78, 71, 13, 10, 26, 10]) {
        bail!("Page.captureScreenshot returned invalid PNG data");
    }
    crate::settings::atomic_write(output_path, &bytes)
        .with_context(|| format!("failed to save screenshot {}", output_path.display()))?;
    Ok(bytes.len() as u64)
}

pub async fn run_periodic_evaluations<F>(
    websocket_url: &str,
    period: Duration,
    mut next_expression: F,
) -> anyhow::Result<()>
where
    F: FnMut() -> anyhow::Result<Option<String>>,

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Retry once after a short delay — transient during navigation/first paint, and the helper is called in a periodic loop where the next tick usually succeeds
  2. Verify the selected target is a real injectable page (pick_page_target) and still alive before capturing
  3. Log the full CDP response when data is missing: an "error" object in it (e.g. 'Cannot take screenshot with 0 width') pinpoints the real browser-side cause
  4. Ensure the window/page has non-zero visible size (not minimized to 0x0) when capturing from a headed browser

Example fix

// before: single-shot capture fails when the page is mid-navigation
let size = capture_screenshot(ws_url, &out).await?;

// after: tolerate one transient empty-payload response
let size = match capture_screenshot(ws_url, &out).await {
    Ok(size) => size,
    Err(e) if e.to_string().contains("no image data") => {
        tokio::time::sleep(Duration::from_millis(500)).await;
        capture_screenshot(ws_url, &out).await?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Validate before calling

// Before capturing: confirm the page target is alive and injectable
let targets = list_targets(port).await?;
let page = pick_page_target(&targets)?; // fails fast on dead/non-page targets
// ensure non-blank state, e.g. wait for first load:
crate::bridge::wait_for_page_ready(&ws_url).await?;

Try / catch

// Transient during navigation/first paint — retry once with backoff
let mut last = None;
for attempt in 0..2 {
    match capture_screenshot(ws_url, &out).await {
        Ok(n) => return Ok(n),
        Err(e) if e.to_string().contains("no image data") => {
            tokio::time::sleep(std::time::Duration::from_millis(500 * (attempt + 1))).await;
            last = Some(e);
        }
        Err(e) => return Err(e),
    }
}
Err(last.unwrap())

Prevention

When it happens

Trigger: Calling the screenshot path against a page target that just navigated, crashed, or was closed before Page.captureScreenshot completed; capturing from a non-renderable target (a background/blank page or one where the compositor produced nothing); a CDP error response ({"error":...}) whose result member is absent, so the data lookup fails; very early capture before the first paint on a freshly opened page.

Common situations: Periodic evaluation (run_periodic_evaluations) firing while the page is mid-navigation or being torn down; headless/new-mode Chrome returning an empty data field for about:blank; browser version change altering capture behavior; DevTools/CDP session taken over by another client that detached the target.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/3239622208c69a9e. Report an issue: GitHub.