{"record":{"id":"c8b10d21614371eb","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-size-changed-while-extracting","errorCode":null,"errorMessage":"Invalid CAB archive: size changed while extracting '${memberPath}'","messagePattern":"Invalid CAB archive: size changed while extracting '(.+?)'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":227,"sourceCode":"\t\t\t\t);\n\t\t\t}\n\t\t\toutput.set(decoded, outputPosition);\n\t\t\toutputPosition += decoded.byteLength;\n\t\t\tposition = payloadEnd;\n\t\t}\n\t\treturn output;\n\t}\n}\n\nclass CabMemberSource implements MemberSource {\n\treadonly #folder: CabFolder;\n\treadonly #offset: number;\n\treadonly #declaredSize: number;\n\n\tconstructor(folder: CabFolder, offset: number, size: number) {\n\t\tthis.#folder = folder;\n\t\tthis.#offset = offset;\n\t\tthis.#declaredSize = size;\n\t}\n\n\tasync read(size: number, memberPath: string): Promise<Uint8Array> {\n\t\tif (size !== this.#declaredSize) {\n\t\t\tthrow new ArchiveError(`Invalid CAB archive: size changed while extracting '${memberPath}'`);\n\t\t}\n\t\tconst folder = await this.#folder.readAll();\n\t\tconst end = this.#offset + size;\n\t\tif (!Number.isSafeInteger(end) || this.#offset < 0 || end > folder.byteLength) {\n\t\t\tthrow new ArchiveError(`Invalid CAB archive: member '${memberPath}' is outside its folder data`);\n\t\t}\n\t\treturn folder.slice(this.#offset, end);\n\t}\n}\n\nasync function readCabArchive(source: ByteSource, options: Parameters<FormatReader>[1]): Promise<ArchiveIndexEntry[]> {\n\tif (source.size < FIXED_HEADER_SIZE) throw new ArchiveError(\"Invalid CAB archive: truncated CFHEADER\");\n\tconst fixed = await readExact(source, 0, FIXED_HEADER_SIZE);","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L209-L245","documentation":"Thrown by CabMemberSource.read when the size argument passed at extraction time differs from the size the CFFILE entry declared when the archive was indexed. The archive index is built once with fixed per-member sizes; this is an internal consistency check indicating the member source is being driven with a stale or mismatched size.","triggerScenarios":"Calling a member's read/storage path with a size different from the entry.size returned in the ArchiveIndexEntry — e.g. using an entry from a previous index run against a new archive, or hand-modifying the size before reading.","commonSituations":"Caching ArchiveIndexEntry objects across archive re-reads; passing entry.size to a different member's source; custom extraction code that overrides the declared size.","solutions":["Always pass the entry.size from the same ArchiveIndexEntry that produced the member source","Do not cache ArchiveIndexEntry/member sources across archive reads; re-index per extraction","Check for code that mutates entry.size or passes a computed size instead of the declared one","If your extraction pipeline legitimately knows a different size, re-read the archive so the index matches"],"exampleFix":"// before\nconst size = files[name]; // stale map from a previous run\nconst data = await entry.storage.source.read(size, name);\n// after\nconst data = await entry.storage.source.read(entry.size, name);","handlingStrategy":"type-guard","validationCode":"// before reading, ensure the size comes from the same indexed entry\nfunction canRead(entry: ArchiveIndexEntry, size: number): boolean {\n  return entry.storage?.type === 'member' && size === entry.size;\n}","typeGuard":"function isMemberEntry(entry: ArchiveIndexEntry): entry is ArchiveIndexEntry & { storage: { type: 'member'; source: MemberSource } } {\n  return entry.storage?.type === 'member';\n}","tryCatchPattern":"try {\n  const data = await entry.storage.source.read(entry.size, entry.path);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('size changed while extracting'))\n    throw new Error('Size used for read does not match the indexed entry — re-index the archive.');\n  throw err;\n}","preventionTips":["Always pass entry.size from the same index entry you got the source from","Never cache index entries across archive re-reads","Do not mutate entry.size or compute your own size before read"],"tags":["archive","cab","api-misuse","consistency"],"backgroundTag":"stale-archive-index","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}