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
- Align versions: run a compute image whose pagestore extension matches the pageserver build (check protocol_version negotiation on both ends)
- If you control the message set, add the tag to PagestreamFeMessageTag (keep in sync with pagestore_client.h) on both sides
- Dump the offending first byte and surrounding buffer to confirm desync vs unknown-tag
- 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
- Version-skew CI: run the matrix of compute images against pageserver builds so unknown tags surface in tests, not prod
- Keep PagestreamFeMessageTag and pagestore_client.h in the same commit when adding message types
- Gate testing-only tags behind the same feature on both producer and consumer builds
- On first protocol error, drop the connection rather than resyncing mid-stream -- misreads cascade
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
- Error: {:?}
- unexpected be message kind in response to getpage request: {
- Remote extensions are not configured
- Non-overlapping bounds: other.max = {} was less than self.mi
- Non-overlappinng bounds: self.max = {} was less than other.m
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/e87e41bcd06917c3.
Report an issue: GitHub.