{"record":{"id":"6de0ec2a391ad7b7","repo":"windmill-labs/windmill","slug":"unexpected-eof","errorCode":null,"errorMessage":"unexpected EOF","messagePattern":"unexpected EOF","errorType":"exception","errorClass":"ConversionError::Io (UnexpectedEof)","httpStatus":null,"severity":"error","filePath":"backend/windmill-trigger-postgres/src/replication_message.rs","lineNumber":264,"sourceCode":"impl Buffer {\n    pub fn new(bytes: Bytes, idx: usize) -> Buffer {\n        Buffer { bytes, idx }\n    }\n\n    fn slice(&self) -> &[u8] {\n        &self.bytes[self.idx..]\n    }\n\n    fn read_cstr(&mut self) -> Result<String, ConversionError> {\n        match self.slice().iter().position(|&x| x == 0) {\n            Some(pos) => {\n                let start = self.idx;\n                let end = start + pos;\n                let cstr = str::from_utf8(&self.bytes[start..end])?.to_owned();\n                self.idx = end + 1;\n                Ok(cstr)\n            }\n            None => Err(ConversionError::Io(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"unexpected EOF\",\n            ))),\n        }\n    }\n}\n\nimpl Read for Buffer {\n    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        let len = {\n            let slice = self.slice();\n            let len = cmp::min(slice.len(), buf.len());\n            buf[..len].copy_from_slice(&slice[..len]);\n            len\n        };\n        self.idx += len;\n        Ok(len)\n    }","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-trigger-postgres/src/replication_message.rs#L246-L282","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match parse(msg_bytes) {\n    Err(ConversionError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {\n        tracing::warn!(\"truncated replication message, resyncing: {e}\");\n        // drop & recreate the slot / restart the stream\n    }\n    Ok(msg) => handle(msg),\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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"],"tags":["postgres","replication","eof","parsing"],"backgroundTag":"unexpected-eof-parse-error","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}