{"record":{"id":"5a5dc6d42244eb7f","repo":"can1357/oh-my-pi","slug":"archive-member-memberpath-is-truncated","errorCode":null,"errorMessage":"Archive member '${memberPath}' is truncated","messagePattern":"Archive member '(.+?)' is truncated","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/unix-ar.ts","lineNumber":38,"sourceCode":"\tsize: number;\n\tmtimeSeconds?: number;\n\tmode?: number;\n}\n\nclass ArMemberSource implements MemberSource {\n\treadonly #source: ByteSource;\n\treadonly #offset: number;\n\n\tconstructor(source: ByteSource, offset: number) {\n\t\tthis.#source = source;\n\t\tthis.#offset = offset;\n\t}\n\n\tasync read(size: number, memberPath: string): Promise<Uint8Array> {\n\t\ttry {\n\t\t\tconst bytes = await this.#source.read(this.#offset, this.#offset + size);\n\t\t\tif (bytes.byteLength !== size) {\n\t\t\t\tthrow new ArchiveError(`Archive member '${memberPath}' is truncated`);\n\t\t\t}\n\t\t\treturn bytes;\n\t\t} catch (error) {\n\t\t\tif (error instanceof ArchiveError) throw error;\n\t\t\tthrow new ArchiveError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n}\n\nfunction decodeAsciiField(bytes: Uint8Array, offset: number, length: number): string {\n\tlet end = offset + length;\n\twhile (end > offset && bytes[end - 1] === 0x20) end--;\n\tlet value = \"\";\n\tfor (let index = offset; index < end; index++) {\n\t\tconst byte = bytes[index]!;\n\t\tif (byte < 0x20 || byte > 0x7e) throw new ArchiveError(\"Invalid ar archive header field\");\n\t\tvalue += String.fromCharCode(byte);\n\t}","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/unix-ar.ts#L20-L56","documentation":"A member reader was asked for `size` bytes at the member's offset, but the underlying source returned fewer bytes — the archive data ends early. The library surfaces this as an ArchiveError naming the member rather than returning a short buffer.","triggerScenarios":"ArReader.read()/readAllBytes called on a truncated .ar file: the header claims a size larger than the bytes actually present in the file/stream.","commonSituations":"Interrupted download or upload of the archive; a hand-written ar file with a wrong/oversized size field; reading while the file is still being written.","solutions":["Re-obtain the archive from a complete, verified source (checksum) and retry","Validate that the file length covers the header-declared member sizes before reading","Fix the writer that emitted the incorrect size field"],"exampleFix":"// before\nconst data = await ar.read(header.size, header.name);\n// after\nif (this.#sourceLength - memberOffset < header.size) {\n\tthrow new Error(`Archive truncated: member ${header.name}`);\n}\nconst data = await ar.read(header.size, header.name);","handlingStrategy":"validation","validationCode":"if (archiveByteLength - memberOffset < header.size) {\n\tthrow new Error(`Truncated archive: '${header.name}' needs ${header.size} bytes`);\n}","typeGuard":null,"tryCatchPattern":"try {\n\tconst data = await reader.read(header.size, header.name);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"is truncated\")) {\n\t\t// re-fetch the archive and verify its checksum\n\t}\n\tthrow err;\n}","preventionTips":["Verify archive checksums/lengths before parsing members","Validate that total declared member sizes fit within the file length","Never parse archives still being written"],"tags":["archive","ar","truncation"],"backgroundTag":"archive-truncated-member","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}