can1357/oh-my-pi · error · ArchiveError

Archive member '${formatArchivePathForError(memberPath)}' is

Error message

Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read

What it means

TarMemberSource.read() refuses to return data for tar members stored with GNU sparse encoding. Sparse files are represented by a map of holes/data extents that this reader does not materialize, so attempting to read the member's content is rejected instead of returning silently wrong bytes. The member path is included (sanitized via formatArchivePathForError) to identify the offending entry.

Source

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

interface PendingTarLink {
	kind: "hard link" | "symlink";
	targetPath: string;
}

class TarMemberSource implements MemberSource {
	readonly #buffer: Uint8Array;
	readonly #dataOffset: number;
	readonly #sparse: boolean;

	constructor(buffer: Uint8Array, dataOffset: number, sparse: boolean) {
		this.#buffer = buffer;
		this.#dataOffset = dataOffset;
		this.#sparse = sparse;
	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		if (this.#sparse) {
			throw new ArchiveError(
				`Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read`,
			);
		}
		if (size > this.#buffer.byteLength - this.#dataOffset) {
			throw new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' is truncated`);
		}
		const bytes = this.#buffer.subarray(this.#dataOffset, this.#dataOffset + size);
		if (bytes.byteLength !== size) {
			throw new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' has an invalid size`);
		}
		return bytes;
	}
}

function readTarString(buffer: Uint8Array, offset: number, length: number): string {
	const limit = Math.min(offset + length, buffer.byteLength);
	let end = offset;
	while (end < limit && buffer[end] !== 0) end++;

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-create the archive without sparse encoding: tar --no-sparse (or omit -S) so files are stored fully materialized
  2. Skip sparse members in your extraction loop — check the entry type before calling read() and handle them separately
  3. Use a tar reader with sparse support if you need the original hole structure
  4. On the source system, zero-out-and-copy the file (cp --sparse=never) before archiving

Example fix

// before: blindly reading every member
for (const entry of index) {
	const data = await entry.source.read(entry.size, entry.path);
}
// after: skip sparse members
for (const entry of index) {
	if (entry.sparse) { console.warn(`skipping sparse member ${entry.path}`); continue; }
	const data = await entry.source.read(entry.size, entry.path);
}
Defensive patterns

Strategy: validation

Validate before calling

// inspect entries before reading; skip anything flagged sparse
for (const entry of index) {
	if (entryIsSparse(entry)) {
		console.warn(`skipping sparse member: ${entry.path}`);
		continue;
	}
}

Type guard

function isSparseMember(entry: { sparse?: boolean }): entry is { sparse: true } {
	return entry.sparse === true;
}

Try / catch

try {
	const data = await entry.source.read(entry.size, entry.path);
} catch (err) {
	if (err instanceof ArchiveError && err.message.includes("sparse file")) {
		throw new Error(`${entry.path} is GNU-sparse; recreate the tar without --sparse or use a sparse-aware reader`);
	}
	throw err;
}

Prevention

When it happens

Trigger: Extracting or reading content of a tar entry whose header flags mark it as GNU sparse (typeflag 'S', or GNU sparse extensions in the ustar header — GNU.sparse.* PAX keys / sparse isextended blocks). The reader parses the member but any call to MemberSource.read(size, path) on it throws.

Common situations: Archives created by GNU tar with --sparse (or -S) on files with large zero regions (VM disks, database files, log files); old backup archives of filesystem images; encountering sparse members while listing works fine but extraction fails.

Related errors


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