can1357/oh-my-pi · error · ArchiveError

Archive file '${label}' is unpacked and requires a filesyste

Error message

Archive file '${label}' is unpacked and requires a filesystem-backed ASAR archive

What it means

Thrown by UnpackedAsarMemberSource.read when an ASAR entry is flagged `unpacked: true` but no filesystem path could be resolved for its sidecar file. Unpacked members live in `<archive>.unpacked/` next to the ASAR file, and their absolute path can only be computed when the archive was opened via `options.archivePath`. Reading such a member from an in-memory or stream-backed source is impossible.

Source

Thrown at packages/utils/src/ar/asar.ts:153

class UnpackedAsarMemberSource implements MemberSource {
	readonly #filePath?: string;
	readonly #size: number;
	readonly #integrity?: AsarIntegrity;

	constructor(filePath: string | undefined, size: number, integrity: AsarIntegrity | undefined) {
		this.#filePath = filePath;
		this.#size = size;
		this.#integrity = integrity;
	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		const label = formatArchivePathForError(memberPath);
		if (size !== this.#size) {
			throw new ArchiveError(`ASAR member '${label}' has an inconsistent size`);
		}
		if (!this.#filePath) {
			throw new ArchiveError(`Archive file '${label}' is unpacked and requires a filesystem-backed ASAR archive`);
		}
		const file = Bun.file(this.#filePath);
		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)}`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Open the archive with its real filesystem path so options.archivePath is set and `<asar>.unpacked/` sidecars can be resolved
  2. Ship the `<archive>.unpacked/` directory alongside the .asar file when distributing
  3. Repack the ASAR without unpacked files (no `unpack` options) if only the single .asar will be available

Example fix

// before
const archive = await readArchive(await Bun.file(app.asar).bytes());
// after
const archive = await readArchiveFromFile("/app/resources/app.asar", { archivePath: "/app/resources/app.asar" });
Defensive patterns

Strategy: fallback

Validate before calling

if (entry.storage?.source instanceof UnpackedAsarMemberSource) {
  // only safe when archive was opened with archivePath
  if (!options.archivePath) throw new Error("unpacked member requires filesystem-backed archive");
}

Type guard

const canReadUnpacked = (entry) => !isUnpacked(entry) || Boolean(options.archivePath);

Try / catch

try { return await readMember(entry); } catch (e) { if (String(e.message).includes("requires a filesystem-backed ASAR")) return readUnpackedFromDisk(`${asarPath}.unpacked/${entry.path}`); throw e; }

Prevention

When it happens

Trigger: Reading an unpacked member of an ASAR that was opened from a stream or memory buffer (no archivePath), so UnpackedAsarMemberSource was constructed with filePath === undefined.

Common situations: Opening a downloaded or bundled .asar from bytes and then reading a member that the packager marked unpacked (common with Electron apps using asar.unpack); embedding ASAR parsing where only raw bytes are available.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3da655cb863a6002. Report an issue: GitHub.