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
- Wait for the file to be non-empty before sending the marker message (poll its size or get a completion signal from the producer).
- Regenerate the source file if its producer failed.
- 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
- Signal file-completion from the producer (temp-file rename pattern) instead of polling paths that may exist empty.
- Treat 0-byte artifacts from upstream tools as failures at generation time.
- Retry sends whose target file was empty, after confirming the producer finished.
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
- Recipient cannot be empty
- Recipient `{trimmed}` does not contain a valid phone number
- TTS returned empty audio
- Slack outbound attachment target must be a local workspace p
- Slack outbound attachment path must be absolute: {target}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c60f77239caf5985.
Report an issue: GitHub.