zeroclaw-labs/zeroclaw · error

WhatsApp marker target {} is empty

Error message

WhatsApp marker target {} is empty

What it means

When a message attachment marker points at a local file, the WhatsApp Web channel reads the file before uploading; a successful read of a zero-byte file bails with the path. This is distinct from a read failure (which surfaces the underlying IO error with context `read WhatsApp marker target ...`).

Source

Thrown at crates/zeroclaw-channels/src/whatsapp_web.rs:1796

                "TTS: sent voice note ({} bytes, ~{}s)",
                audio_len, estimated_seconds
            )
        );
        Ok(())
    }

    #[cfg(feature = "whatsapp-web")]
    async fn send_media_marker(
        client: &whatsapp_rust::Client,
        to: &wacore_binary::jid::Jid,
        marker: &WhatsAppMediaMarker,
        path: &Path,
    ) -> Result<()> {
        let bytes = tokio::fs::read(path)
            .await
            .with_context(|| format!("read WhatsApp marker target {}", path.display()))?;
        if bytes.is_empty() {
            anyhow::bail!("WhatsApp marker target {} is empty", path.display());
        }

        let media_type = marker.kind.media_type();
        let mime = marker.kind.mime_for_path(path);

        use whatsapp_rust::upload::UploadOptions;
        let upload = client
            .upload(bytes, media_type, UploadOptions::default())
            .await
            .map_err(|e| anyhow::Error::msg(format!("WhatsApp media upload failed: {e}")))?;

        let media_key = upload.media_key_vec();
        let file_enc_sha256 = upload.file_enc_sha256_vec();
        let file_sha256 = upload.file_sha256_vec();
        let outgoing = match marker.kind {
            WhatsAppMediaKind::Image => waproto::whatsapp::Message {
                image_message: Some(Box::new(waproto::whatsapp::message::ImageMessage {
                    url: Some(upload.url),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Wait for the file to be non-empty before sending the marker message (poll its size or get a completion signal from the producer).
  2. Regenerate the source file if its producer failed.
  3. Retry the send once the producer has finished writing.

Example fix

// before: send the marker immediately after asking a producer to write the file
channel.send(&SendMessage::new(marker_text, to)).await?;

// after: verify the file has content first
let len = tokio::fs::metadata(&path).await?.len();
if len == 0 {
    anyhow::bail!("attachment {} was not written yet", path.display());
}
channel.send(&SendMessage::new(marker_text, to)).await?;
Defensive patterns

Strategy: validation

Validate before calling

let len = tokio::fs::metadata(&path).await?.len();
anyhow::ensure!(len > 0, "attachment {} is empty; producer may have failed", path.display());
channel.send(&SendMessage::new(marker_text, to)).await

Type guard

async fn non_empty_file(path: &std::path::Path) -> bool {
    matches!(tokio::fs::metadata(path).await, Ok(m) if m.len() > 0)
}

Prevention

When it happens

Trigger: A marker referencing an existing but empty (0-byte) file — e.g. a download that was started but aborted, a placeholder file created ahead of content, or a producer that failed silently.

Common situations: Racing a file producer (sending before the file was flushed); upstream tools writing empty artifacts on failure; cleanup jobs truncating files in place while sends reference them.

Related errors


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