astrid-runtime/astrid · error
Message too large from kernel
Error message
Message too large from kernel: {len} bytes What it means
read_message reads a 4-byte big-endian length prefix and rejects any declared length over 50 MiB before allocating the payload buffer. This protects the uplink process from corrupt or hostile kernel-side streams advertising absurd sizes that would otherwise cause huge allocations or OOM.
Solutions
- Check for prior stream corruption: any read that did not consume an entire frame leaves the stream misaligned; restart the connection to resynchronize.
- Confirm both ends use the same framing protocol (4-byte BE length prefix, 50 MiB cap); align versions.
- If messages legitimately exceed 50 MiB, raise the cap (or chunk messages) on both sides.
- Log the offending len value and surrounding bytes to identify whether the peer is corrupt or hostile.
Example fix
// before: sending an oversized message that the peer will reject
let payload = build_huge_blob(); // > 50 MiB
write_frame(&payload).await?;
// after: chunk large payloads into frames under the 50 MiB limit
const MAX: usize = 50 * 1024 * 1024 - 1024;
for chunk in payload.chunks(MAX) {
write_frame(chunk).await?;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Enforce the size cap on the sender side before framing a message
const MAX: usize = 50 * 1024 * 1024;
if payload.len() > MAX {
return Err(anyhow::anyhow!("payload {} exceeds {} cap", payload.len(), MAX));
} Try / catch
match client.read_message().await {
Ok(Some(msg)) => handle(msg),
Ok(None) => { /* clean EOF */ },
Err(e) if e.to_string().contains("Message too large from kernel") => {
// stream is desynchronized; the only safe move is reconnect
client.reconnect().await?;
}
Err(e) => return Err(e),
} Prevention
- Always read frames with read_exact so the stream never desynchronizes.
- Chunk messages well under the 50 MiB cap on the sending side.
- Keep framing protocol versions identical between client and daemon.
- Log the offending length prefix to distinguish corruption from hostility.
When it happens
Trigger: Calling read_message when the peer's length prefix decodes to more than 50*1024*1024 bytes — stream desynchronization (reading garbage as a length), a corrupted/truncated frame, or a peer protocol that frames messages differently.
Common situations: Byte-stream desync after a prior partial read left extra bytes in the stream; a daemon/kernel component of a different version using a different framing (e.g. different header size or endianness); a malicious or buggy peer sending a garbage length prefix.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- Handshake response too large
- daemon closed the response stream before the final marker
- daemon rejected status request
- daemon returned an unexpected status response
- detached FUSE service exceeded the startup response size
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/5957baf430d9bdb3.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-uplink/src/socket_client.rs:264
/// kernel's `astrid.v1.capsules_loaded` broadcast, whose
/// [`IpcPayload::RawJson`] inner value is emitted without the
/// `type` discriminator) are logged at `debug` and skipped. Without
/// this tolerance interactive clients would die on the first
/// broadcast.
///
/// # Errors
/// Returns an error if the connection is unrecoverable (over-large
/// frame, IO failure mid-read).
pub async fn read_message(&mut self) -> Result<Option<IpcMessage>> {
loop {
let mut len_buf = [0u8; 4];
if self.read_half.read_exact(&mut len_buf).await.is_err() {
return Ok(None);
}
let len = u32::from_be_bytes(len_buf) as usize;
if len > 50 * 1024 * 1024 {
anyhow::bail!("Message too large from kernel: {len} bytes");
}
let mut payload = vec![0u8; len];
self.read_half.read_exact(&mut payload).await?;
if let Ok(message) = serde_json::from_slice::<IpcMessage>(&payload) {
return Ok(Some(message));
}
let preview = String::from_utf8_lossy(&payload[..payload.len().min(120)]);
tracing::debug!(
preview = %preview,
"skipping unparseable frame from daemon"
);
}
}
/// Read the next length-prefixed frame as raw bytes, without
/// attempting to deserialize. Used by [`crate::admin_client`] whenView on GitHub (pinned to affd8760f4)