{"record":{"id":"e87e41bcd06917c3","repo":"neondatabase/neon","slug":"invalid-tag-tag","errorCode":null,"errorMessage":"invalid tag {tag}","messagePattern":"invalid tag (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/pageserver_api/src/pagestream_api.rs","lineNumber":329,"sourceCode":"        //\n        // TODO: consider using protobuf or serde bincode for less error prone\n        // serialization.\n        let msg_tag = body.read_u8()?;\n        let (reqid, request_lsn, not_modified_since) = match protocol_version {\n            PagestreamProtocolVersion::V2 => (\n                0,\n                Lsn::from(body.read_u64::<BigEndian>()?),\n                Lsn::from(body.read_u64::<BigEndian>()?),\n            ),\n            PagestreamProtocolVersion::V3 => (\n                body.read_u64::<BigEndian>()?,\n                Lsn::from(body.read_u64::<BigEndian>()?),\n                Lsn::from(body.read_u64::<BigEndian>()?),\n            ),\n        };\n\n        match PagestreamFeMessageTag::try_from(msg_tag)\n            .map_err(|tag: u8| anyhow::anyhow!(\"invalid tag {tag}\"))?\n        {\n            PagestreamFeMessageTag::Exists => {\n                Ok(PagestreamFeMessage::Exists(PagestreamExistsRequest {\n                    hdr: PagestreamRequest {\n                        reqid,\n                        request_lsn,\n                        not_modified_since,\n                    },\n                    rel: RelTag {\n                        spcnode: body.read_u32::<BigEndian>()?,\n                        dbnode: body.read_u32::<BigEndian>()?,\n                        relnode: body.read_u32::<BigEndian>()?,\n                        forknum: body.read_u8()?,\n                    },\n                }))\n            }\n            PagestreamFeMessageTag::Nblocks => {\n                Ok(PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/pageserver_api/src/pagestream_api.rs#L311-L347","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: testing-only message sent to a production build\n// tag 99 -> invalid tag 99\n\n// after: gate test traffic behind a testing-built pageserver, or\n// register the new tag on both sides (pagestream_api.rs + pagestore_client.h)\nenum PagestreamFeMessageTag {\n    Exists = 0,\n    Nblocks = 1,\n    GetPage = 2,\n    DbSize = 3,\n    GetSlruSegment = 4,\n    NewMessage = 5, /* future tags above this line */\n}","handlingStrategy":"try-catch","validationCode":"// If you proxy or test the pagestream protocol, validate the leading tag byte\n// before handing the buffer to PagestreamFeMessage::parse:\nconst VALID_FE_TAGS: &[u8] = &[0, 1, 2, 3, 4]; // 99 only on testing builds\n\nfn pagestream_tag_supported(tag: u8, testing_build: bool) -> bool {\n    VALID_FE_TAGS.contains(&tag) || (testing_build && tag == 99)\n}","typeGuard":"fn is_valid_pagestream_fe_tag(tag: u8, testing: bool) -> bool {\n    matches!(tag, 0..=4) || (testing && tag == 99)\n}","tryCatchPattern":"match PagestreamFeMessage::parse(&mut body, protocol_version) {\n    Ok(msg) => handle(msg).await,\n    Err(e) if format!(\"{e:#}\").contains(\"invalid tag\") => {\n        // Protocol-level mismatch: do not retry; log versions on both ends and kill the connection\n        tracing::error!(\n            error = %e,\n            compute_protocol = ?protocol_version,\n            \"pagestream tag mismatch: compute/pageserver version skew or stream desync\"\n        );\n        return Err(QueryError::Disconnected(ConnectionError::Protocol(\n            ProtocolError::Protocol(format!(\"invalid pagestream tag: {e:#}\")),\n        )));\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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"],"tags":["rust","pagestream","protocol","serialization","version-skew"],"backgroundTag":"protocol-decode-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}