stalwartlabs/stalwart · warning · io::Error(InvalidData)
Invalid response: {raw_response}
Error message
Invalid response: {raw_response} What it means
Raised when a Pyzor UDP response parsed successfully but one or more required fields (code, count, wl-count) are missing, leaving them at their sentinel values u32::MAX / u64::MAX. pyzor_send_message treats an incomplete result as invalid and includes the whole raw payload in the error message.
Source
Thrown at crates/spam-filter/src/modules/pyzor.rs:134
std::io::ErrorKind::InvalidData,
format!("Invalid line: {raw_response}"),
)
})?;
} else if k.eq_ignore_ascii_case("wl-count") {
response.wl_count = v.trim().parse().map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid line: {raw_response}"),
)
})?;
}
}
}
if response.code != u32::MAX && response.count != u64::MAX && response.wl_count != u64::MAX {
Ok(response)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid response: {raw_response}"),
))
}
}
trait PyzorWrite {
fn write_all(&mut self, data: &[u8]);
}
impl PyzorWrite for Vec<u8> {
fn write_all(&mut self, data: &[u8]) {
self.extend_from_slice(data);
}
}
impl PyzorWrite for Sha1 {
fn write_all(&mut self, data: &[u8]) {View on GitHub (pinned to e962003857)
Solutions
- Verify the Pyzor server address/port configuration matches a genuine Pyzor server
- Probe the server manually and inspect the complete raw response
- Log raw_response from the error to determine which fields are missing
- Switch to a healthy Pyzor mirror or upgrade the server
- Handle the PyzorError event in pyzor_check so spam scoring degrades gracefully
Defensive patterns
Strategy: validation
Validate before calling
// check the reply contains all required fields before use
fn is_complete_pyzor_response(raw: &str) -> bool {
let has = |key: &str| raw.lines().any(|l| {
l.split_once(':').map(|(k, _)| k.eq_ignore_ascii_case(key)).unwrap_or(false)
});
has("code") && has("count") && has("wl-count")
} Try / catch
match pyzor_check(&config, &message).await {
Ok(score) => score,
Err(e) if e.details().contains("Invalid response") => {
tracing::warn!("incomplete pyzor reply; skipping signal");
Default::default()
}
Err(e) => return Err(e),
} Prevention
- Monitor the configured Pyzor server for degraded/partial replies and fail over to a mirror
- Verify the endpoint port speaks Pyzor protocol, not another service
- Log the raw response (included in the error) to see which fields go missing
- Never block mail delivery on incomplete Pyzor data
When it happens
Trigger: pyzor_send_message receives a valid UTF-8 datagram that lacks code:, count:, or wl-count: lines — e.g. an empty datagram, a bare error string, or a partial protocol reply.
Common situations: Overloaded or degraded Pyzor server returning partial replies; wrong port hitting a service that responds but not in Pyzor protocol; datagram truncation losing trailing fields; server rejecting a digest without full counts.
Related errors
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/d89c17fa41e66b7f.
Report an issue: GitHub.