atuinsh/atuin · error · std::io::Error
frame exceeds maximum length
Error message
frame exceeds maximum length
What it means
Returned by read_frame in the pty-proxy protocol crate. Every frame starts with a 5-byte header: one frame-type byte plus a u32 big-endian payload length. If that length exceeds MAX_FRAME_LEN (1 MiB, protocol.rs:70), read_frame returns io::ErrorKind::InvalidData with this message rather than allocating a hostile or corrupt buffer. It almost always means the byte stream is not actually frame-encoded data, or the two endpoints disagree on the protocol.
Source
Thrown at crates/atuin-pty-proxy/src/protocol.rs:152
/// Read one frame. Returns `Ok(None)` on a clean EOF at a frame boundary.
///
/// Unknown frame types are returned as-is: the transport layer does not
/// decide policy (the server closes on unknown client frames; clients skip
/// unknown server frames for forward compatibility).
///
/// # Errors
///
/// Fails on EOF mid-frame, on a length above [`MAX_FRAME_LEN`], or on any
/// underlying read error.
pub fn read_frame(reader: &mut impl Read) -> io::Result<Option<(u8, Vec<u8>)>> {
let mut header = [0u8; 5];
if !read_exact_or_eof(reader, &mut header)? {
return Ok(None);
}
let frame_type = header[0];
let len = u32::from_be_bytes([header[1], header[2], header[3], header[4]]) as usize;
if len > MAX_FRAME_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"frame exceeds maximum length",
));
}
let mut payload = vec![0u8; len];
reader.read_exact(&mut payload)?;
Ok(Some((frame_type, payload)))
}
/// Fill `buf` completely. Returns `Ok(false)` on EOF before the first byte,
/// `Ok(true)` when full; EOF partway through is an [`io::ErrorKind::UnexpectedEof`].
fn read_exact_or_eof(reader: &mut impl Read, buf: &mut [u8]) -> io::Result<bool> {
let mut filled = 0;
while filled < buf.len() {
match reader.read(&mut buf[filled..]) {
Ok(0) => {
if filled == 0 {
return Ok(false);View on GitHub (pinned to 202f6ad98e)
Solutions
- Verify both endpoints run the same atuin/pty-proxy version and speak the V2 frame protocol
- Confirm the reader is attached to the correct endpoint and that the peer sends the magic greeting before frames
- Treat the connection as poisoned: close it and reconnect rather than trying to resync mid-stream
- If writing a sender, ensure payloads are chunked under 1 MiB (encode_frame already asserts this on the write side)
Example fix
// before
let (frame_type, payload) = read_frame(&mut stream)?.expect("frame");
// after
match read_frame(&mut stream)? {
Some((frame_type, payload)) => { /* handle */ }
None => { /* clean EOF at frame boundary */ }
}
// and on write: assert payload.len() <= MAX_FRAME_LEN before encode_frame Defensive patterns
Strategy: try-catch
Validate before calling
// Before reading frames, confirm the peer speaks V2 by sniffing the magic greeting
let mut first = [0u8; 4];
let n = read_with_timeout(&mut stream, &mut first)?;
if classify_greeting(&first[..n]) == Greeting::Legacy {
// do not feed this stream to read_frame
} Try / catch
match read_frame(&mut stream) {
Ok(Some(frame)) => { /* handle */ }
Ok(None) => { /* clean close */ }
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
// length prefix > 1 MiB: stream is desynced or wrong protocol.
// Do NOT continue reading; close and reconnect.
}
Err(e) => return Err(e.into()),
} Prevention
- Pin both ends of the proxy to the same Atuin version
- Always write frames via encode_frame so headers and lengths stay consistent
- Chunk large payloads under MAX_FRAME_LEN (1 MiB) before encoding
- Validate the peer's greeting before entering the frame loop
When it happens
Trigger: Calling read_frame on a socket/stream carrying something other than pty-proxy frames (raw TLS, HTTP, shell output); a desync where a previous frame was misparsed so the reader is now interpreting payload bytes as a header; a version mismatch where one side emits a different header layout; a malicious peer writing a crafted length prefix.
Common situations: Connecting the proxy client to the wrong port or a TLS-enabled endpoint; a legacy peer (the post-accept greeting/classify_greeting distinguishes V2 from legacy) that never speaks frames; upgrading one side of the proxy pair and not the other; feeding a captured/truncated pcap replay into the parser.
Related errors
- eof in the middle of a frame
- Empty theme directory override and could not find theme else
- Failed to deserialize theme: {}
- Parent requested but we hit the recursion limit
- key file vanished immediately after a concurrent write
AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16).
Data as JSON: /api/errors/d92e9529f23ed268.
Report an issue: GitHub.