astrid-runtime/astrid · error
local IPC stream ended within a frame
Error message
local IPC stream ended within a frame
What it means
read_message reads from the local IPC stream in 8 KiB chunks until a complete length-prefixed frame is buffered. If the reader returns 0 bytes (EOF) while a partial frame is still buffered, the stream ended mid-message and the library cannot complete the frame, so it raises UnexpectedEof. A clean EOF with an empty buffer returns Ok(None) instead.
Solutions
- Inspect why the peer process terminated mid-frame: check its exit status, stderr, and crash logs
- Ensure the peer writes each frame atomically (single write_all of length+payload) and flushes before exit
- Verify the length prefix on the peer matches the exact payload byte length
- Add liveness/restart handling for the child process and treat this as a peer-crash signal
Example fix
// before: peer exits right after enqueueing a write
spawn(async move { writer.write(msg).await; });
process::exit(0);
// after: flush before exiting
spawn(async move {
writer.write_all(&frame).await.expect("write frame");
writer.flush().await.expect("flush");
}); Defensive patterns
Strategy: try-catch
Try / catch
match read_message(&mut reader).await {
Ok(None) => {/* peer closed cleanly between frames */},
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
log::error!("peer died mid-frame: {e}");
// check peer exit status and restart
}
Ok(Some(msg)) => handle(msg),
Err(e) => return Err(e.into()),
} Prevention
- Ensure the peer flushes all writes before exiting
- Write each frame with a single write_all call (length + payload)
- Monitor child process health and restart on abnormal exit
- Avoid killing the peer while writes are in flight
When it happens
Trigger: The peer process crashed or was killed after writing part of a frame; the peer closed the socket without flushing the remaining bytes of a large message; a write side that failed mid-frame due to its own I/O error; peer wrote a length prefix larger than the payload it actually wrote.
Common situations: Child process segfaulting or exiting early during startup; OOM killer terminating the peer mid-write; incorrect framing on the peer for messages larger than the 8 KiB read chunk; CI environments killing processes on timeout.
Related errors
- daemon closed the response stream before the final marker
- daemon connection closed before command result
- Admin request timed out after
- an Astrid daemon appears to be running but its uplink is…
- an Astrid daemon appears to be running but its uplink is…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/d6ee92a5b48ddfc3.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-uplink/src/native/framing.rs:63
let message =
serde_json::from_slice(&self.buffered[4..frame_len]).map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("invalid IPC message: {error}"),
)
})?;
self.buffered.drain(..frame_len);
return Ok(Some(message));
}
}
let mut chunk = [0_u8; 8192];
let read = self.reader.read(&mut chunk).await?;
if read == 0 {
if self.buffered.is_empty() {
return Ok(None);
}
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"local IPC stream ended within a frame",
));
}
self.buffered.extend_from_slice(&chunk[..read]);
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use astrid_types::Topic;
use astrid_types::ipc::IpcPayload;
use tokio::io::AsyncWriteExt;
use uuid::Uuid;
View on GitHub (pinned to affd8760f4)