can1357/oh-my-pi · warning · ArchiveError
Unpacked ASAR file '${label}' changed while being read
Error message
Unpacked ASAR file '${label}' changed while being read What it means
Thrown when the bytes actually read from the unpacked sidecar differ in length from the size obtained by stat() moments earlier. This indicates the file was modified concurrently during the read, so the returned buffer would be inconsistent with the header.
Source
Thrown at packages/utils/src/ar/asar.ts:174
const stat = await file.stat().catch(() => {
throw new ArchiveError(`Unpacked ASAR file '${label}' was not found`);
});
if (stat.isDirectory()) {
throw new ArchiveError(`Unpacked ASAR file '${label}' is a directory`);
}
if (stat.size !== this.#size) {
throw new ArchiveError(
`Unpacked ASAR file '${label}' size differs from its archive header (${stat.size} != ${this.#size} bytes)`,
);
}
let bytes: Uint8Array;
try {
bytes = await file.bytes();
} catch (error) {
throw new ArchiveError(`Failed to read unpacked ASAR file '${label}': ${describeError(error)}`);
}
if (bytes.byteLength !== this.#size) {
throw new ArchiveError(`Unpacked ASAR file '${label}' changed while being read`);
}
verifyIntegrity(bytes, this.#integrity, memberPath);
return bytes;
}
}
async function readAsarIndex(source: ByteSource, options: FormatReadOptions): Promise<ArchiveIndexEntry[]> {
if (!Number.isSafeInteger(source.size) || source.size < ASAR_JSON_OFFSET) {
throw invalidAsar("truncated header");
}
const sizePickle = await source.read(0, ASAR_PICKLE_PREFIX_SIZE);
if (sizePickle.byteLength !== ASAR_PICKLE_PREFIX_SIZE) {
throw invalidAsar("truncated size pickle");
}
if (readUInt32LE(sizePickle, 0) !== 4) {
throw invalidAsar("invalid size pickle");
}View on GitHub (pinned to 9690622007)
Solutions
- Stop processes that mutate the .unpacked tree while the app reads it; retry the read once the writer finishes
- Read from a stable installed copy rather than a live-build directory
- Use file locking or read a snapshot copy for assets that change
Example fix
// before await member.read(...) // while `vite build --watch` writes assets // after await buildComplete; // wait for writer, then read const bytes = await member.read(entry.size, entry.path);
Defensive patterns
Strategy: retry
Try / catch
for (let i = 0; i < 3; i++) {
try { return await readMember(entry); }
catch (e) { if (e instanceof ArchiveError && e.message.includes("changed while being read")) { await Bun.sleep(50); continue; } throw e; }
}
throw new Error(`file ${entry.path} is being modified concurrently`); Prevention
- Stop watch/build processes writing .unpacked during runtime reads
- Run a single app instance against one install directory
- Snapshot assets before reading if updaters may rewrite them
When it happens
Trigger: Another process wrote/truncated/replaced the .unpacked file between stat() and file.bytes(); a build watch process regenerated the file while the app was reading it.
Common situations: Dev hot-reload tooling touching unpacked assets at runtime; two app instances sharing one install directory; package managers or updaters swapping files while the app runs.
Related errors
- OAuth refresh ownership was lost before persistence
- No credential with id=${id}
- session header changed during stats cleanup: ${session.path}
- Daemon ${spec.name} is already starting
- RPC message snapshot does not match current messages
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/35a7d50616715124.
Report an issue: GitHub.