can1357/oh-my-pi · error · ArchiveError

Archive sparse metadata is truncated

Error message

Archive sparse metadata is truncated

What it means

Thrown while parsing a GNU sparse member (type flag 'S'): when the sparse-map 'isextended' byte is set, the header is followed by continuation blocks listing sparse extents, and this error fires when the buffer ends before the continuation chain completes. It means the archive's sparse metadata is cut off mid-chain, so the member's real data offset can't be located. Like the other truncation guards, it indicates incomplete archive bytes rather than bad options.

Source

Thrown at packages/utils/src/ar/tar.ts:506

		if (longName !== undefined) name = longName;
		if (longLink !== undefined) linkName = longLink;
		const paxPath = paxAttribute(globalPax, localPax, "path");
		if (paxPath !== undefined) name = paxPath;
		const paxLinkPath = paxAttribute(globalPax, localPax, "linkpath");
		if (paxLinkPath !== undefined) linkName = paxLinkPath;
		const paxSize = paxAttribute(globalPax, localPax, "size");
		if (paxSize !== undefined) size = parsePaxSize(paxSize, "member size");
		const paxSparseName = paxAttribute(globalPax, localPax, "GNU.sparse.name");
		if (paxSparseName !== undefined) name = paxSparseName;
		let displaySize = size;
		const paxSparseRealSize = paxAttribute(globalPax, localPax, "GNU.sparse.realsize");
		if (paxSparseRealSize !== undefined) displaySize = parsePaxSize(paxSparseRealSize, "sparse real size");
		const sparse = typeFlag === "S" || paxDeclaresSparse(globalPax, localPax);
		if (typeFlag === "S" && buffer[headerOffset + GNU_SPARSE_ISEXTENDED_OFFSET] === 1) {
			let extended = true;
			while (extended) {
				if (offset + BLOCK_SIZE > buffer.byteLength) {
					throw new ArchiveError("Archive sparse metadata is truncated");
				}
				extended = buffer[offset + GNU_SPARSE_CONT_ISEXTENDED_OFFSET] === 1;
				offset += BLOCK_SIZE;
			}
		}
		const dataOffset = offset;
		const memberDataBlocks = paddedSize(size);
		if (memberDataBlocks > buffer.byteLength - dataOffset) {
			throw new ArchiveError(`Archive member '${formatArchivePathForError(name)}' is truncated`);
		}
		offset += memberDataBlocks;
		longName = undefined;
		longLink = undefined;
		localPax = undefined;

		const isDirectory = typeFlag === "5" || name.endsWith("/");
		const normalizedPath = normalizeArchiveEntryPath(name);
		if (!normalizedPath) continue;

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-transfer the archive and verify size/hash against the source.
  2. Confirm the decompression step completed (gzip/brotli EOF) before buffering for parse.
  3. Extract with GNU tar first (`tar -xSf`) or repack non-sparsely (`tar -cf`) if you must hand the bytes to this parser.
  4. Do not truncate/split sparse tar archives by byte count; keep the whole file.

Example fix

// before: buffer may be a partial chunk
const buf = chunks[0];
const entries = readTarEntriesFromBuffer(buf, options);
// after: only parse the fully received archive
const buf = new Uint8Array(await Bun.file(path).arrayBuffer());
const entries = readTarEntriesFromBuffer(buf, options);
Defensive patterns

Strategy: validation

Validate before calling

// require the whole file (sparse tars must be complete) before parsing
const stat = await fs.stat(archivePath);
if (stat.size < (expectedSize ?? 0)) throw new Error(`sparse archive incomplete: ${stat.size} < ${expectedSize}`);
const bytes = new Uint8Array(await Bun.file(archivePath).arrayBuffer());
const entries = readTarEntriesFromBuffer(bytes, { limits });

Try / catch

try {
  const entries = readTarEntriesFromBuffer(bytes, { limits });
} catch (err) {
  if (err instanceof ArchiveError && /sparse metadata is truncated/.test(err.message)) {
    throw new Error('Sparse tar archive is incomplete; re-transfer without truncating');
  }
  throw err;
}

Prevention

When it happens

Trigger: Parsing a tar created with GNU `tar -S` (sparse) where the buffer ends inside the sparse-extension block chain — e.g. a truncated download or single volume of a sparse multi-volume archive.

Common situations: Sparse backups of VM disks, database files, or docker layer blobs (large files with holes) that were incompletely transferred; cutting a sparse tar mid-file with head/dd.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c5922ac0bc1b69b1. Report an issue: GitHub.