{"record":{"id":"442572c920bd201f","repo":"neondatabase/neon","slug":"unknown-version","errorCode":null,"errorMessage":"Unknown version {}","messagePattern":"Unknown version (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"libs/postgres_ffi/src/lib.rs","lineNumber":281,"sourceCode":") -> Result<Bytes, SerializeError> {\n    assert_eq!(segno, lsn.segment_number(WAL_SEGMENT_SIZE));\n\n    dispatch_pgversion!(\n        pg_version,\n        pgv::xlog_utils::generate_wal_segment(segno, system_id, lsn)\n    )\n}\n\npub fn generate_pg_control(\n    pg_control_bytes: &[u8],\n    checkpoint_bytes: &[u8],\n    lsn: Lsn,\n    pg_version: PgMajorVersion,\n) -> anyhow::Result<(Bytes, u64, bool)> {\n    dispatch_pgversion!(\n        pg_version,\n        pgv::xlog_utils::generate_pg_control(pg_control_bytes, checkpoint_bytes, lsn),\n        anyhow::bail!(\"Unknown version {}\", pg_version)\n    )\n}\n\n// PG timeline is always 1, changing it doesn't have any useful meaning in Neon.\n//\n// NOTE: this is not to be confused with Neon timelines; different concept!\n//\n// It's a shaky assumption, that it's always 1. We might import a\n// PostgreSQL data directory that has gone through timeline bumps,\n// for example. FIXME later.\npub const PG_TLI: u32 = 1;\n\n//  See TransactionIdIsNormal in transam.h\npub const fn transaction_id_is_normal(id: TransactionId) -> bool {\n    id > pg_constants::FIRST_NORMAL_TRANSACTION_ID\n}\n\n// See TransactionIdPrecedes in transam.c","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/postgres_ffi/src/lib.rs#L263-L299","documentation":"generate_pg_control dispatches on PgMajorVersion via the dispatch_pgversion! macro, which only compiles bindings for PG14, PG15, PG16, and PG17 (see the pgversions list in postgres_ffi/src/lib.rs). Any other major version falls through to the supplied handler, here anyhow::bail!('Unknown version {}'). The timeline's pg_control data cannot be interpreted by this build.","triggerScenarios":"Attaching or generating pg_control for a timeline whose recorded major version is outside 14-17 (e.g. a PG18 or PG13 data directory imported into this pageserver), or a corrupted/wrong-endianness pg_control whose version field decodes to an unknown value.","commonSituations":"New PostgreSQL major released before the neon bindings catch up; importing an old tenant archive; downgrading a pageserver binary against newer timelines; tests feeding synthetic pg_control bytes with arbitrary version fields.","solutions":["Re-attach/import the timeline on a pageserver build that includes bindings for that major version (update neon)","If the version looks impossible (e.g. 0 or 65535), suspect a corrupted pg_control or a wrong-offset read -- inspect the raw file","Recreate the timeline with a supported major version if the data is disposable","When building from source, confirm the vendored postgres bindings for the needed version are present"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use pageserver_api::models::Pagebe...; // PgMajorVersion lives in postgres_ffi\n\nfn is_supported_pg_major(v: PgMajorVersion) -> bool {\n    // must match dispatch_pgversion!'s pgversions list\n    matches!(\n        v,\n        PgMajorVersion::PG14 | PgMajorVersion::PG15 | PgMajorVersion::PG16 | PgMajorVersion::PG17\n    )\n}\n\n// Check before any pg_control interpretation:\nanyhow::ensure!(\n    is_supported_pg_major(timeline_pg_version),\n    \"unsupported PostgreSQL version {timeline_pg_version}; this build supports 14-17\"\n);","typeGuard":"fn is_supported_pg_major(v: PgMajorVersion) -> bool {\n    matches!(v, PgMajorVersion::PG14..=PgMajorVersion::PG17)\n}","tryCatchPattern":"match postgres_ffi::generate_pg_control(&control, &checkpoint, lsn, ver) {\n    Ok(out) => out,\n    Err(e) if format!(\"{e:#}\").starts_with(\"Unknown version\") => {\n        // Not retryable: this timeline needs a pageserver built with bindings for that major\n        tracing::error!(pg_version = %ver, timeline = %timeline_id, \"{e:#}\");\n        return Err(e.context(\n            \"timeline uses a PostgreSQL major this build cannot handle; \\\n             reattach on an updated pageserver or recreate the timeline\"\n        ));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate pg major versions at tenant attach time and refuse early with an explicit unsupported-version error","Keep compute images and pageserver builds upgraded together when adopting a new PostgreSQL major","Expose the supported version range in metrics/logs at startup so mismatches are diagnosable","Never hand-craft pg_control bytes in tests; reuse fixtures from a supported major"],"tags":["rust","postgres-ffi","pg-control","version-mismatch","timeline"],"backgroundTag":"unsupported-postgres-version","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}