n0-computer/iroh · error · Error
FrameNotAllowedInVersion
FrameNotAllowedInVersion
Error message
Received a frame not allowed in this protocol version.
What it means
A Health frame was received while the negotiated protocol version was not V1. Health frames only exist in protocol version 1; in later versions they are rejected with FrameNotAllowedInVersion because the peer is speaking a frame type foreign to the negotiated version.
Solutions
- Upgrade both client and relay to the same iroh-relay version so version negotiation agrees.
- If you must support the legacy peer, negotiate ProtocolVersion::V1 for that connection.
- Check that no manual frame injection or test harness sends Health frames on V2+ connections.
- Inspect the negotiated protocol version in the client handshake logs before decoding frames.
Example fix
// before: always sending Health regardless of version
conn.send_frame(&Frame::Health { problem: "overloaded".into() });
// after: only on V1
if protocol_version == ProtocolVersion::V1 {
conn.send_frame(&Frame::Health { problem: "overloaded".into() });
} else {
conn.send_frame(&Frame::Status(Status::healthy()));
} Defensive patterns
Strategy: validation
Validate before calling
if protocol_version != ProtocolVersion::V1 && frame_type == FrameType::Health {
return Err(RelayError::frame_not_allowed_in_version(frame_type, protocol_version));
} Type guard
fn frame_allowed(ft: FrameType, v: ProtocolVersion) -> bool {
match ft {
FrameType::Health => v == ProtocolVersion::V1,
_ => true,
}
} Try / catch
match result {
Err(Error::FrameNotAllowedInVersion) => {
tracing::warn!("peer sent V1-only frame on {protocol_version:?}; upgrading peer required");
negotiate_or_disconnect();
}
other => other,
} Prevention
- Gate frame sending on the negotiated protocol version
- Upgrade all clients and relays together
- Add an integration test that runs the highest supported version handshake
When it happens
Trigger: Frame::from_bytes decodes FrameType::Health when protocol_version != ProtocolVersion::V1 — e.g. the client negotiated V2+ but the remote still sends legacy Health frames.
Common situations: Mixed-version deployments where a newer client connects to an old relay (or vice versa) with mismatched version negotiation, or pinned old crate versions after an upgrade.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- UnexpectedFrameType
- InvalidFrame
- FrameTooLarge
- The relay is rate-limiting this endpoint; outbound relay…
- NoObservedAddr
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/9865b3337605824e.
Report an issue: GitHub.
Appendix: source
Thrown at iroh-relay/src/protos/relay.rs:440
FrameType::EndpointGone => {
ensure!(content.len() == EndpointId::LENGTH, Error::InvalidFrame);
let endpoint_id = cache.key_from_slice(content.as_ref())?;
Self::EndpointGone(endpoint_id)
}
FrameType::Ping => {
ensure!(content.len() == 8, Error::InvalidFrame);
let mut data = [0u8; 8];
data.copy_from_slice(&content[..8]);
Self::Ping(data)
}
FrameType::Pong => {
ensure!(content.len() == 8, Error::InvalidFrame);
let mut data = [0u8; 8];
data.copy_from_slice(&content[..8]);
Self::Pong(data)
}
FrameType::Health => {
ensure!(
protocol_version == ProtocolVersion::V1,
Error::FrameNotAllowedInVersion
);
let problem = std::str::from_utf8(&content)?.to_owned();
Self::Health { problem }
}
FrameType::Restarting => {
ensure!(content.len() == 4 + 4, Error::InvalidFrame);
let reconnect_in = u32::from_be_bytes(
content[..4]
.try_into()
.map_err(|_| e!(Error::InvalidFrame))?,
);
let try_for = u32::from_be_bytes(
content[4..]
.try_into()
.map_err(|_| e!(Error::InvalidFrame))?,
);View on GitHub (pinned to 2b4de030ce)