Hmbown/CodeWhale · error · anyhow::Error

lane exit receipt {} exceeds {} bytes

Error message

lane exit receipt {} exceeds {} bytes

What it means

read_lane_exit_receipt stats the receipt file and refuses to read it if it is larger than MAX_EXIT_RECEIPT_BYTES (4 KiB). The log proxy writes receipts far below this bound, so an oversized file signals corruption, truncation-garbage, or a foreign file sitting at lane_exit_receipt_path(log_path). The guard protects the reconciler from loading arbitrary data.

Source

Thrown at crates/lane/src/runtime.rs:524

        .map_err(|_| anyhow::anyhow!("lane proxy stderr logger panicked"))?;
    let mut exit_code = exit_status_code(status);
    if let Some(error) = stdout_result.err().or_else(|| stderr_result.err()) {
        append_proxy_failure(&log_path, &lane_id, &error)?;
        exit_code = LANE_PROXY_FAILURE_EXIT_CODE;
    }
    write_lane_exit_receipt(&receipt_path, &receipt_tmp_path, &lane_id, exit_code)?;
    Ok(exit_code)
}

fn read_lane_exit_receipt(log_path: &Path, lane_id: &str) -> Result<Option<LaneExitReceipt>> {
    let path = lane_exit_receipt_path(log_path);
    let metadata = match std::fs::metadata(&path) {
        Ok(metadata) => metadata,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(err) => return Err(err).with_context(|| format!("stat {}", path.display())),
    };
    if metadata.len() > MAX_EXIT_RECEIPT_BYTES {
        bail!(
            "lane exit receipt {} exceeds {} bytes",
            path.display(),
            MAX_EXIT_RECEIPT_BYTES
        );
    }
    let bytes = std::fs::read(&path).with_context(|| format!("read {}", path.display()))?;
    let receipt: LaneExitReceipt =
        serde_json::from_slice(&bytes).with_context(|| format!("parse {}", path.display()))?;
    if receipt.lane_id != lane_id {
        bail!(
            "lane exit receipt {} belongs to {}, expected {}",
            path.display(),
            receipt.lane_id,
            lane_id
        );
    }
    Ok(Some(receipt))
}

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Inspect the file named in the message and delete it — a missing receipt is tolerated (read returns Ok(None)) and reconciliation falls back to tmux session state
  2. Verify the log proxy binary is the runtime's own, not a wrapper adding output
  3. Use a fresh log directory per lane generation to avoid inheriting foreign files
Defensive patterns

Strategy: fallback

Validate before calling

fn receipt_readable(path: &std::path::Path) -> bool {
    std::fs::metadata(path)
        .map(|m| m.len() <= 4 * 1024)
        .unwrap_or(false)
}

Try / catch

match read_lane_exit_receipt(&log_path, &lane_id) {
    Ok(receipt) => { /* use exit code */ }
    Err(err) if err.to_string().contains("exceeds") => {
        // Oversized/corrupt receipt: remove it and reconcile via tmux state instead.
        let _ = std::fs::remove_file(lane_exit_receipt_path(&log_path));
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Reconciling a lane (e.g. after a crash) where the receipt path under the lane's log directory holds a file > 4096 bytes — concatenated/truncated writes, a different tool's output, or a receipt from an incompatible format.

Common situations: A crashed writer leaving a partially-written receipt that was appended to across runs; log directories reused or shared between tools; disk corruption; the log proxy being replaced by a script that writes extra content to the same path.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/206ac4afc8decbeb. Report an issue: GitHub.