n0-computer/iroh · error · Error
InvalidFrame
InvalidFrame
Error message
Invalid frame encoding
What it means
Error::InvalidFrame from ServerToClientUrlOrEndpoint (health/status) frame decoding: from_bytes requires at least one byte to read the discriminant. An empty buffer cannot encode any health frame, so decoding fails immediately.
Solutions
- Check bytes.is_empty() before calling from_bytes and treat empty input as end-of-stream rather than a frame.
- Verify the framing layer reads the declared frame length fully before decoding.
- Inspect relay connectivity — repeated empty frames suggest a broken or closed connection.
Example fix
// before
let frame = HealthFrame::from_bytes(bytes)?;
// after
if bytes.is_empty() {
return Ok(None); // end of stream / no frame
}
let frame = HealthFrame::from_bytes(bytes)?; Defensive patterns
Strategy: validation
Validate before calling
fn decode_health(bytes: &Bytes) -> Option<HealthFrame> {
if bytes.is_empty() { return None; }
Some(HealthFrame::from_bytes(bytes.clone()).ok()?)
} Type guard
fn has_frame(bytes: &Bytes) -> bool { !bytes.is_empty() } Prevention
- Treat empty payloads as end-of-stream, not frames.
- Read the full declared frame length before decoding.
- Monitor for repeated empty frames — usually a closing connection.
When it happens
Trigger: Decoding an empty Bytes buffer as a relay health/status frame — e.g. a zero-length message received from the relay or a caller slicing out an empty payload.
Common situations: Relay connection closing and delivering a truncated/empty final message; slicing bugs that pass an empty byte slice to from_bytes.
Understand the failure class
Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.
Related errors
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/5e6df38c9356d7de.
Report an issue: GitHub.
Appendix: source
Thrown at iroh-relay/src/protos/relay.rs:163
impl Status {
#[cfg(feature = "server")]
fn write_to<O: BufMut>(&self, mut dst: O) -> O {
match self {
Status::Healthy => dst.put_u8(0),
Status::SameEndpointIdConnected => dst.put_u8(1),
Status::RateLimited => dst.put_u8(2),
Status::Unknown(discriminant) => dst.put_u8(*discriminant),
}
dst
}
#[cfg(feature = "server")]
fn encoded_len(&self) -> usize {
1
}
fn from_bytes(mut bytes: Bytes) -> Result<Self, Error> {
ensure!(!bytes.is_empty(), Error::InvalidFrame);
let discriminant = bytes.get_u8();
match discriminant {
0 => Ok(Self::Healthy),
1 => Ok(Self::SameEndpointIdConnected),
2 => Ok(Self::RateLimited),
n => Ok(Self::Unknown(n)),
}
}
}
/// Messages that clients send to relays.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ClientToRelayMsg {
/// Request from the client to the server to reply to the
/// other side with a [`RelayToClientMsg::Pong`] with the given payload.
Ping([u8; 8]),
/// Reply to a [`RelayToClientMsg::Ping`] from a serverView on GitHub (pinned to 2b4de030ce)