neondatabase/neon · error

invalid tag {tag}

Error message

invalid tag {tag}

What it means

PagestreamFeMessage::parse reads the first byte of each pagestream COPY submessage and converts it to PagestreamFeMessageTag. Valid tags are 0=Exists, 1=Nblocks, 2=GetPage, 3=DbSize, 4=GetSlruSegment (and 99=Test only in testing builds). Any other byte fails the TryFrom<u8> conversion and becomes this error, printed with the tag's decimal value.

Source

Thrown at libs/pageserver_api/src/pagestream_api.rs:329

        //
        // TODO: consider using protobuf or serde bincode for less error prone
        // serialization.
        let msg_tag = body.read_u8()?;
        let (reqid, request_lsn, not_modified_since) = match protocol_version {
            PagestreamProtocolVersion::V2 => (
                0,
                Lsn::from(body.read_u64::<BigEndian>()?),
                Lsn::from(body.read_u64::<BigEndian>()?),
            ),
            PagestreamProtocolVersion::V3 => (
                body.read_u64::<BigEndian>()?,
                Lsn::from(body.read_u64::<BigEndian>()?),
                Lsn::from(body.read_u64::<BigEndian>()?),
            ),
        };

        match PagestreamFeMessageTag::try_from(msg_tag)
            .map_err(|tag: u8| anyhow::anyhow!("invalid tag {tag}"))?
        {
            PagestreamFeMessageTag::Exists => {
                Ok(PagestreamFeMessage::Exists(PagestreamExistsRequest {
                    hdr: PagestreamRequest {
                        reqid,
                        request_lsn,
                        not_modified_since,
                    },
                    rel: RelTag {
                        spcnode: body.read_u32::<BigEndian>()?,
                        dbnode: body.read_u32::<BigEndian>()?,
                        relnode: body.read_u32::<BigEndian>()?,
                        forknum: body.read_u8()?,
                    },
                }))
            }
            PagestreamFeMessageTag::Nblocks => {
                Ok(PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Align versions: run a compute image whose pagestore extension matches the pageserver build (check protocol_version negotiation on both ends)
  2. If you control the message set, add the tag to PagestreamFeMessageTag (keep in sync with pagestore_client.h) on both sides
  3. Dump the offending first byte and surrounding buffer to confirm desync vs unknown-tag
  4. For testing-only tags, ensure the pageserver is built with the 'testing' feature

Example fix

// before: testing-only message sent to a production build
// tag 99 -> invalid tag 99

// after: gate test traffic behind a testing-built pageserver, or
// register the new tag on both sides (pagestream_api.rs + pagestore_client.h)
enum PagestreamFeMessageTag {
    Exists = 0,
    Nblocks = 1,
    GetPage = 2,
    DbSize = 3,
    GetSlruSegment = 4,
    NewMessage = 5, /* future tags above this line */
}
Defensive patterns

Strategy: try-catch

Validate before calling

// If you proxy or test the pagestream protocol, validate the leading tag byte
// before handing the buffer to PagestreamFeMessage::parse:
const VALID_FE_TAGS: &[u8] = &[0, 1, 2, 3, 4]; // 99 only on testing builds

fn pagestream_tag_supported(tag: u8, testing_build: bool) -> bool {
    VALID_FE_TAGS.contains(&tag) || (testing_build && tag == 99)
}

Type guard

fn is_valid_pagestream_fe_tag(tag: u8, testing: bool) -> bool {
    matches!(tag, 0..=4) || (testing && tag == 99)
}

Try / catch

match PagestreamFeMessage::parse(&mut body, protocol_version) {
    Ok(msg) => handle(msg).await,
    Err(e) if format!("{e:#}").contains("invalid tag") => {
        // Protocol-level mismatch: do not retry; log versions on both ends and kill the connection
        tracing::error!(
            error = %e,
            compute_protocol = ?protocol_version,
            "pagestream tag mismatch: compute/pageserver version skew or stream desync"
        );
        return Err(QueryError::Disconnected(ConnectionError::Protocol(
            ProtocolError::Protocol(format!("invalid pagestream tag: {e:#}")),
        )));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A compute (postgres with the Neon pagestore extension) speaks a pagestream protocol version whose message set differs from this pageserver build -- e.g. a newer extension sending a tag >4, or tag 99 hitting a production build compiled without the 'testing' feature. Also produced by a misaligned byte stream after a previous short read or protocol desync.

Common situations: Version skew between compute image and pageserver (new message type added in a newer release); testing-only messages sent against a production pageserver; stream corruption from a proxy or a partial read bug; fuzzing or hand-crafted COPY payloads.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/e87e41bcd06917c3. Report an issue: GitHub.