windmill-labs/windmill · error · ConversionError::Io (UnexpectedEof)
unexpected EOF
Error message
unexpected EOF
What it means
`read_cstr` scans for the NUL terminator in the replication message buffer while parsing length-prefixed, null-terminated strings (e.g. relation names). If the buffer ends before a NUL byte is found, it returns UnexpectedEof 'unexpected EOF'. This means the message is truncated relative to its declared structure.
Source
Thrown at backend/windmill-trigger-postgres/src/replication_message.rs:264
impl Buffer {
pub fn new(bytes: Bytes, idx: usize) -> Buffer {
Buffer { bytes, idx }
}
fn slice(&self) -> &[u8] {
&self.bytes[self.idx..]
}
fn read_cstr(&mut self) -> Result<String, ConversionError> {
match self.slice().iter().position(|&x| x == 0) {
Some(pos) => {
let start = self.idx;
let end = start + pos;
let cstr = str::from_utf8(&self.bytes[start..end])?.to_owned();
self.idx = end + 1;
Ok(cstr)
}
None => Err(ConversionError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected EOF",
))),
}
}
}
impl Read for Buffer {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = {
let slice = self.slice();
let len = cmp::min(slice.len(), buf.len());
buf[..len].copy_from_slice(&slice[..len]);
len
};
self.idx += len;
Ok(len)
}View on GitHub (pinned to e474e8803c)
Solutions
- Restart the replication stream / recreate the slot to get a clean, aligned message stream.
- Check for a prior parse error that desynchronized offsets — fix the root misparse.
- Verify the pgoutput protocol version options match the parser implementation.
- If reproducible, capture the offending message bytes and compare against the pgoutput spec to find the length mismatch.
Defensive patterns
Strategy: try-catch
Try / catch
match parse(msg_bytes) {
Err(ConversionError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
tracing::warn!("truncated replication message, resyncing: {e}");
// drop & recreate the slot / restart the stream
}
Ok(msg) => handle(msg),
Err(e) => return Err(e.into()),
} Prevention
- Recreate the replication slot after any parse desync — never continue parsing a misaligned stream
- Check for earlier parse errors in logs that shift byte offsets
- Ensure the replication connection isn't going through corrupting middleboxes
- Validate pgoutput protocol version options against the parser implementation
When it happens
Trigger: parse() calls read_cstr on a replication message whose bytes run out before the string's NUL terminator — typically when an earlier field was misread (wrong length) or the message was truncated in transport.
Common situations: Corrupted or truncated replication stream frames; a preceding parse step consuming the wrong number of bytes shifting all subsequent offsets; network layer delivering partial messages without the framing layer compensating.
Related errors
- unknown replication message byte `{}`
- unknown replica identity byte `{}`
- unexpected tuple byte `{}`
- unknown tuple byte `{}`
- unknown tuple tag `{}`
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/6de0ec2a391ad7b7.
Report an issue: GitHub.