{"record":{"id":"2253d6bec372e1d2","repo":"zeroclaw-labs/zeroclaw","slug":"whatsapp-location-marker-must-be-lat-lng-name-a","errorCode":null,"errorMessage":"WhatsApp location marker must be `lat,lng[,name[,address]]` with in-range WGS84 coordinates","messagePattern":"WhatsApp location marker must be `lat,lng\\[,name\\[,address\\]\\]` with in-range WGS84 coordinates","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/whatsapp.rs","lineNumber":844,"sourceCode":"\n        let mut failed_markers = 0usize;\n        for (_, target) in &location_markers {\n            match crate::util::WhatsAppLocation::parse(target) {\n                Some(loc) => self.post_message(location_message_body(to, &loc)).await?,\n                None => {\n                    ::zeroclaw_log::record!(\n                        WARN,\n                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                            .with_attrs(::serde_json::json!({\"reason\": \"invalid_location\"})),\n                        \"whatsapp: location marker target is malformed or outside WGS84 range\"\n                    );\n                    failed_markers += 1;\n                }\n            }\n        }\n        if failed_markers == location_markers.len() && text.is_empty() {\n            anyhow::bail!(\n                \"WhatsApp location marker must be `lat,lng[,name[,address]]` with in-range WGS84 coordinates\"\n            );\n        }\n\n        Ok(())\n    }\n\n    async fn send_choice(\n        &self,\n        recipient: &str,\n        prompt: &str,\n        options: &[(String, String)],\n    ) -> anyhow::Result<()> {\n        let trimmed_prompt = prompt.trim();\n        // No options → send the prompt as plain text (or no-op when both\n        // empty); never render a \"(reply with name or number)\" hint with\n        // nothing under it. Mirrors the trait default's empty guard.\n        if options.is_empty() {","sourceCodeStart":826,"sourceCodeEnd":862,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/whatsapp.rs#L826-L862","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Fix the marker target format: `lat,lng` plus optional quoted `name` and `address` fields, with lat in -90..=90 and lng in -180..=180.","Include plain text alongside markers so one bad pin cannot make the entire message fail.","Validate coordinates at the source (template/LLM tooling) before building the marker."],"exampleFix":"// before\nlet content = \"[LOCATION:91.0,0.0]\"; // lat out of WGS84 range\nchannel.send(&SendMessage::new(content, to)).await?;\n\n// after\nlet content = \"Meet me here: [LOCATION:40.7128,-74.0060,\\\"Statue of Liberty\\\",\\\"New York, NY\\\"]\";\nchannel.send(&SendMessage::new(content, to)).await?;","handlingStrategy":"validation","validationCode":"fn valid_location_target(t: &str) -> bool {\n    let mut parts = t.split(',');\n    let lat = parts.next().and_then(|p| p.trim().parse::<f64>().ok());\n    let lng = parts.next().and_then(|p| p.trim().parse::<f64>().ok());\n    matches!((lat, lng),\n        (Some(la), Some(lo))\n            if (-90.0..=90.0).contains(&la) && (-180.0..=180.0).contains(&lo))\n}\n\n// before sending, check every LOCATION marker in the content\nassert!(extract_location_targets(&content).iter().all(|t| valid_location_target(t)));","typeGuard":"fn looks_like_location_target(target: &str) -> bool {\n    let mut parts = target.split(',');\n    let lat = parts.next().and_then(|p| p.trim().parse::<f64>().ok());\n    let lng = parts.next().and_then(|p| p.trim().parse::<f64>().ok());\n    matches!((lat, lng), (Some(la), Some(lo))\n        if (-90.0..=90.0).contains(&la) && (-180.0..=180.0).contains(&lo))\n}","tryCatchPattern":"match channel.send(&msg).await {\n    Err(e) if e.to_string().contains(\"location marker must be\") => {\n        // degrade gracefully: strip markers, deliver plain text\n        let plain = strip_location_markers(&msg.content);\n        channel.send(&SendMessage::new(plain, &msg.recipient)).await\n    }\n    other => other,\n}","preventionTips":["Always emit marker content with a plain-text lead-in so one bad pin never fails the whole send.","Quote names/addresses containing commas (`[LOCATION:40.7,-74,\"ACME, Inc.\"]`).","If an LLM generates markers, constrain the tool schema to `lat: number, lng: number` with documented ranges."],"tags":["whatsapp","location","parsing","validation","wgs84","rust"],"backgroundTag":"geolocation-parse-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}