{"record":{"id":"5fd6187598b7e5c2","repo":"can1357/oh-my-pi","slug":"archive-member-formatarchivepathforerror-member-5fd618","errorCode":null,"errorMessage":"Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read","messagePattern":"Archive member '(.+?)' is a sparse file and cannot be read","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":69,"sourceCode":"interface PendingTarLink {\n\tkind: \"hard link\" | \"symlink\";\n\ttargetPath: string;\n}\n\nclass TarMemberSource implements MemberSource {\n\treadonly #buffer: Uint8Array;\n\treadonly #dataOffset: number;\n\treadonly #sparse: boolean;\n\n\tconstructor(buffer: Uint8Array, dataOffset: number, sparse: boolean) {\n\t\tthis.#buffer = buffer;\n\t\tthis.#dataOffset = dataOffset;\n\t\tthis.#sparse = sparse;\n\t}\n\n\tasync read(size: number, memberPath: string): Promise<Uint8Array> {\n\t\tif (this.#sparse) {\n\t\t\tthrow new ArchiveError(\n\t\t\t\t`Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read`,\n\t\t\t);\n\t\t}\n\t\tif (size > this.#buffer.byteLength - this.#dataOffset) {\n\t\t\tthrow new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' is truncated`);\n\t\t}\n\t\tconst bytes = this.#buffer.subarray(this.#dataOffset, this.#dataOffset + size);\n\t\tif (bytes.byteLength !== size) {\n\t\t\tthrow new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' has an invalid size`);\n\t\t}\n\t\treturn bytes;\n\t}\n}\n\nfunction readTarString(buffer: Uint8Array, offset: number, length: number): string {\n\tconst limit = Math.min(offset + length, buffer.byteLength);\n\tlet end = offset;\n\twhile (end < limit && buffer[end] !== 0) end++;","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-create the archive without sparse encoding: tar --no-sparse (or omit -S) so files are stored fully materialized","Skip sparse members in your extraction loop — check the entry type before calling read() and handle them separately","Use a tar reader with sparse support if you need the original hole structure","On the source system, zero-out-and-copy the file (cp --sparse=never) before archiving"],"exampleFix":"// before: blindly reading every member\nfor (const entry of index) {\n\tconst data = await entry.source.read(entry.size, entry.path);\n}\n// after: skip sparse members\nfor (const entry of index) {\n\tif (entry.sparse) { console.warn(`skipping sparse member ${entry.path}`); continue; }\n\tconst data = await entry.source.read(entry.size, entry.path);\n}","handlingStrategy":"validation","validationCode":"// inspect entries before reading; skip anything flagged sparse\nfor (const entry of index) {\n\tif (entryIsSparse(entry)) {\n\t\tconsole.warn(`skipping sparse member: ${entry.path}`);\n\t\tcontinue;\n\t}\n}","typeGuard":"function isSparseMember(entry: { sparse?: boolean }): entry is { sparse: true } {\n\treturn entry.sparse === true;\n}","tryCatchPattern":"try {\n\tconst data = await entry.source.read(entry.size, entry.path);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"sparse file\")) {\n\t\tthrow new Error(`${entry.path} is GNU-sparse; recreate the tar without --sparse or use a sparse-aware reader`);\n\t}\n\tthrow err;\n}","preventionTips":["Create archives with tar --no-sparse (never pass -S/--sparse) when consumers use this reader","Enumerate entries and check for sparse flags before attempting extraction","Document to archive producers that GNU sparse members are not readable here","Handle the error per-member so one sparse file does not abort the whole extraction run"],"tags":["tar","sparse-files","unsupported-feature","archive"],"backgroundTag":"sparse-tar-member-unsupported","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}