{"record":{"id":"1e4ec7d162db44e6","repo":"can1357/oh-my-pi","slug":"invalid-arj-archive-truncated-data","errorCode":null,"errorMessage":"Invalid ARJ archive: truncated data","messagePattern":"Invalid ARJ archive: truncated data","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":238,"sourceCode":"\tif (size < 30 || size > ARJ_MAX_BASIC_HEADER || bytes.byteLength < size + 8) return false;\n\tconst body = bytes.subarray(4, 4 + size);\n\treturn body[0]! >= 30 && body[0]! <= size && body[6] === 2 && crc32(body) === u32(bytes, 4 + size);\n}\n\n/** Index an ARJ archive and lazily decode stored, static-Huffman, and fast-LZSS members. */\nexport const readArj: FormatReader = async (\n\tsource: ByteSource,\n\toptions: FormatReadOptions,\n): Promise<ArchiveIndexEntry[]> => {\n\tassertInMemorySize(source.size, options.limits);\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await readAllBytes(source);\n\t} catch (error) {\n\t\tif (error instanceof ArchiveError) throw error;\n\t\tthrow new ArchiveError(`Unable to read ARJ archive: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (bytes.byteLength !== source.size) throw new ArchiveError(\"Invalid ARJ archive: truncated data\");\n\tif (!sniffArj(bytes)) throw new ArchiveError(\"Invalid ARJ archive header\");\n\n\tconst main = parseArjBlock(bytes, 0, options);\n\tif (main.isEnd) throw new ArchiveError(\"Invalid ARJ archive: missing main header\");\n\tconst mainFirstHeaderSize = bytes[main.bodyStart]!;\n\tif (mainFirstHeaderSize < 30 || mainFirstHeaderSize > main.bodySize || bytes[main.bodyStart + 6] !== 2) {\n\t\tthrow new ArchiveError(\"Invalid ARJ main header\");\n\t}\n\tconst mainFlags = bytes[main.bodyStart + 4]!;\n\tif ((mainFlags & 0x01) !== 0) throw new ArchiveError(\"Encrypted ARJ archives are unsupported\");\n\tif ((mainFlags & 0x04) !== 0) throw new ArchiveError(\"Multi-volume ARJ archives are unsupported\");\n\n\tconst entries: ArchiveIndexEntry[] = [];\n\tlet offset = main.nextOffset;\n\tlet metadataSize = main.metadataSize;\n\tlet parsedCount = 0;\n\tfor (;;) {\n\t\tconst block = parseArjBlock(bytes, offset, options);","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L220-L256","documentation":"This ArchiveError is thrown when the number of bytes actually read from the ByteSource is less than the source's declared size — the archive data is truncated relative to what was promised. The library compares bytes.byteLength against source.size after fully reading.","triggerScenarios":"Calling readArj(source, options) where the underlying file/stream yields fewer bytes than source.size reported (file shrank between stat and read, partial download, reader stream ended early).","commonSituations":"Interrupted downloads, files still being written, rsync/copy interrupted, cloud-sync placeholders not fully materialized.","solutions":["Re-download or re-copy the archive; the file is incomplete.","Re-stat the file and pass a fresh, correct size to the ByteSource.","Compare the on-disk file size to the expected size from the archive provider.","If reading a stream, ensure the stream is fully drained and not closed early."],"exampleFix":"// before\nconst stat = await fs.stat(path);\nconst entries = await readArj({ path, size: stat.size }, options);\n// after\nconst stat = await fs.stat(path);\nif (stat.size < expectedMinSize) throw new Error(`${path} looks truncated (${stat.size} bytes)`);\nconst entries = await readArj({ path, size: stat.size }, options);","handlingStrategy":"validation","validationCode":"const stat = await fs.stat(path);\nif (stat.size === 0) throw new Error(`${path} is empty`);\n// pass stat.size as the ByteSource size so the truncation check can fire accurately\nconst entries = await readArj({ path, size: stat.size }, options);","typeGuard":null,"tryCatchPattern":"try {\n  const entries = await readArj(source, options);\n} catch (e) {\n  if (e instanceof ArchiveError && e.message.includes('truncated data')) {\n    throw new Error('archive is incomplete; re-download it');\n  }\n  throw e;\n}","preventionTips":["Verify downloaded file size against the publisher's expected size","Wait for downloads/copies to finish before parsing","Pass an accurate source.size so the truncation invariant is meaningful"],"tags":["archive","truncation","arj","io"],"backgroundTag":"file-truncated","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}