{"record":{"id":"607edf3d8813f830","repo":"can1357/oh-my-pi","slug":"archive-member-formatarchivepathforerror-member-607edf","errorCode":null,"errorMessage":"Archive member '${formatArchivePathForError(memberPath)}' is truncated","messagePattern":"Archive member '(.+?)' is truncated","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":74,"sourceCode":"class 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++;\n\treturn TEXT_DECODER.decode(buffer.subarray(offset, end));\n}\n\nfunction bytesEqualAscii(bytes: Uint8Array, value: string): boolean {\n\treturn bytes.byteLength === value.length && bytesMatchAscii(bytes, 0, value);","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L56-L92","documentation":"TarMemberSource.read() checks that the requested size does not extend past the end of the in-memory tar buffer relative to the member's data offset. This throw means the tar header declared a member larger than the actual bytes remaining in the archive — the archive is truncated or corrupted. It fires before slicing, so no short data is ever silently returned.","triggerScenarios":"Reading a tar member whose header size field exceeds buffer.byteLength - dataOffset: the archive download/copy was cut short, the file was truncated at rest, or a corrupt size field (bad octal/PAX size) in the header inflates the declared size.","commonSituations":"Interrupted downloads or scp/rsync transfers; tar files concatenated or edited incorrectly; archives stored on failing disks; sizes corrupted by a writer bug (non-terminated octal fields) making a member appear larger than the file.","solutions":["Re-obtain the archive (re-download/re-copy) and verify its size/checksum against the source manifest","Validate the whole tar by walking all headers before extraction to catch truncation early","Check the tar size field for corruption if the file size matches expectations but reads still fail","Surface a user-facing 'archive is incomplete' message instead of retrying reads against a truncated buffer"],"exampleFix":"// before: extracting without integrity check\nconst reader = await readTar(await readAllBytes(src));\nawait extractAll(reader);\n// after: verify total size against manifest before parsing\nif (bytes.byteLength !== manifest[\"bundle.tar\"].size) {\n\tthrow new Error(\"tar truncated; re-download\");\n}\nconst reader = await readTar(bytes);","handlingStrategy":"validation","validationCode":"const stat = await fs.stat(path);\nif (manifest[archiveName] && stat.size !== manifest[archiveName].size) {\n\tthrow new Error(`tar truncated on disk (${stat.size} != ${manifest[archiveName].size}); re-download`);\n}","typeGuard":null,"tryCatchPattern":"try {\n\tconst data = await entry.source.read(entry.size, entry.path);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"is truncated\")) {\n\t\tthrow new Error(`tar is incomplete; member ${entry.path} extends past end of file — re-obtain the archive`, { cause: err });\n\t}\n\tthrow err;\n}","preventionTips":["Verify download completeness (size + checksum) before parsing a tar","Walk all tar headers at open time to validate the archive before extracting members","Avoid rsync/scp/concat operations that can silently truncate archives","Treat this error as data corruption: never retry the read; re-obtain the file"],"tags":["tar","truncation","corruption","archive"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}