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

  1. Upgrade both client and relay to the same iroh-relay version so version negotiation agrees.
  2. If you must support the legacy peer, negotiate ProtocolVersion::V1 for that connection.
  3. Check that no manual frame injection or test harness sends Health frames on V2+ connections.
  4. 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

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


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)