zeroclaw-labs/zeroclaw · error

WhatsApp location marker must be `lat,lng[,name[,address]]`

Error message

WhatsApp location marker must be `lat,lng[,name[,address]]` with in-range WGS84 coordinates

What it means

The WhatsApp Cloud `send` path extracts `[LOCATION:...]` attachment markers from message content and converts each into a native location pin. Each marker target must parse as `lat,lng[,name[,address]]` with WGS84-range coordinates (latitude -90..=90, longitude -180..=180). This error fires only when every marker in the message failed to parse AND no plain text remains — i.e. nothing was sendable at all. Malformed markers mixed with real text are logged (WARN, reason `invalid_location`) and skipped, and the send still succeeds.

Source

Thrown at crates/zeroclaw-channels/src/whatsapp.rs:844

        let mut failed_markers = 0usize;
        for (_, target) in &location_markers {
            match crate::util::WhatsAppLocation::parse(target) {
                Some(loc) => self.post_message(location_message_body(to, &loc)).await?,
                None => {
                    ::zeroclaw_log::record!(
                        WARN,
                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                            .with_attrs(::serde_json::json!({"reason": "invalid_location"})),
                        "whatsapp: location marker target is malformed or outside WGS84 range"
                    );
                    failed_markers += 1;
                }
            }
        }
        if failed_markers == location_markers.len() && text.is_empty() {
            anyhow::bail!(
                "WhatsApp location marker must be `lat,lng[,name[,address]]` with in-range WGS84 coordinates"
            );
        }

        Ok(())
    }

    async fn send_choice(
        &self,
        recipient: &str,
        prompt: &str,
        options: &[(String, String)],
    ) -> anyhow::Result<()> {
        let trimmed_prompt = prompt.trim();
        // No options → send the prompt as plain text (or no-op when both
        // empty); never render a "(reply with name or number)" hint with
        // nothing under it. Mirrors the trait default's empty guard.
        if options.is_empty() {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the marker target format: `lat,lng` plus optional quoted `name` and `address` fields, with lat in -90..=90 and lng in -180..=180.
  2. Include plain text alongside markers so one bad pin cannot make the entire message fail.
  3. Validate coordinates at the source (template/LLM tooling) before building the marker.

Example fix

// before
let content = "[LOCATION:91.0,0.0]"; // lat out of WGS84 range
channel.send(&SendMessage::new(content, to)).await?;

// after
let content = "Meet me here: [LOCATION:40.7128,-74.0060,\"Statue of Liberty\",\"New York, NY\"]";
channel.send(&SendMessage::new(content, to)).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_location_target(t: &str) -> bool {
    let mut parts = t.split(',');
    let lat = parts.next().and_then(|p| p.trim().parse::<f64>().ok());
    let lng = parts.next().and_then(|p| p.trim().parse::<f64>().ok());
    matches!((lat, lng),
        (Some(la), Some(lo))
            if (-90.0..=90.0).contains(&la) && (-180.0..=180.0).contains(&lo))
}

// before sending, check every LOCATION marker in the content
assert!(extract_location_targets(&content).iter().all(|t| valid_location_target(t)));

Type guard

fn looks_like_location_target(target: &str) -> bool {
    let mut parts = target.split(',');
    let lat = parts.next().and_then(|p| p.trim().parse::<f64>().ok());
    let lng = parts.next().and_then(|p| p.trim().parse::<f64>().ok());
    matches!((lat, lng), (Some(la), Some(lo))
        if (-90.0..=90.0).contains(&la) && (-180.0..=180.0).contains(&lo))
}

Try / catch

match channel.send(&msg).await {
    Err(e) if e.to_string().contains("location marker must be") => {
        // degrade gracefully: strip markers, deliver plain text
        let plain = strip_location_markers(&msg.content);
        channel.send(&SendMessage::new(plain, &msg.recipient)).await
    }
    other => other,
}

Prevention

When it happens

Trigger: A `SendMessage` whose content consists solely of location markers, all malformed: `[LOCATION:40.7128]` (missing longitude), `[LOCATION:91.0,0.0]` (latitude out of range), `[LOCATION:not-a-number,0.0]`, or an empty marker target.

Common situations: An LLM or template emitting only one coordinate; decimal-comma locales producing `40,7128,-74,0060`; latitude/longitude swapped or scaled wrongly; forgetting that names containing commas must be quoted (`[LOCATION:40.7,-74,"ACME, Inc."]` is valid).

Related errors


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