neondatabase/neon · error

not an image or delta layer: {layer_file_path}

Error message

not an image or delta layer: {layer_file_path}

What it means

When rewriting a layer summary, the ctl first tries the file as an image layer and then as a delta layer; the delta path tolerates a magic mismatch and falls through. If neither path recognizes the file's magic bytes, the file is neither a recognized image nor delta layer, and the command bails. The file is corrupt, truncated, in an incompatible on-disk format, or simply not a layer file.

Source

Thrown at pageserver/ctl/src/layers.rs:243

            let res = DeltaLayer::rewrite_summary(
                layer_file_path,
                rewrite_closure!(delta_layer::Summary),
                &ctx,
            )
            .await;
            match res {
                Ok(()) => {
                    println!("Successfully rewrote summary of delta layer {layer_file_path}");
                    return Ok(());
                }
                Err(delta_layer::RewriteSummaryError::MagicMismatch) => (), // fallthrough
                Err(delta_layer::RewriteSummaryError::Other(e)) => {
                    return Err(e);
                }
            }

            anyhow::bail!("not an image or delta layer: {layer_file_path}");
        }
    }
}

fn print_layer_file(idx: usize, layer_file: &LayerFile) {
    println!(
        "[{:3}]  key:{}-{}\n       lsn:{}-{}\n       delta:{}",
        idx,
        layer_file.key_range.start,
        layer_file.key_range.end,
        layer_file.lsn_range.start,
        layer_file.lsn_range.end,
        layer_file.is_delta,
    );
}

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Verify the file: check its size and first bytes; image and delta layers start with distinct magic values, and note that main.rs already tries pg control format before this path.
  2. Re-download the layer from remote storage to rule out truncation or corruption.
  3. Confirm the ctl build matches the pageserver version that wrote the layer.
Defensive patterns

Strategy: fallback

Validate before calling

// check the magic bytes before handing the file to layer tooling
async fn looks_like_layer_file(path: &Utf8Path) -> bool {
    let mut magic = [0u8; 1];
    tokio::fs::File::open(path).await.ok()
        .and_then(|mut f| std::io::Read::read_exact(&mut f, &mut magic).ok())
        .is_some()
}

Try / catch

// main.rs already tries pg control first; keep the same fallback shape in wrappers
if let Err(e) = print_layerfile(&path).await {
    if e.to_string().starts_with("not an image or delta layer") {
        // try the next candidate format or re-download the layer from remote storage
        re_download_and_retry(&path).await?;
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Passing a path that is not a layer file (for example index_part.json or a pg control file), a truncated download, or a layer written by a pageserver with a different layer file version.

Common situations: Wrong file passed on the command line; interrupted downloads from remote storage; ctl and pageserver built from different versions with changed on-disk formats.

Related errors


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