can1357/oh-my-pi · error · ArchiveError

Archive file '${normalizedPath}' not found

Error message

Archive file '${normalizedPath}' not found

What it means

`readFile` resolved the link path successfully, but no entry exists at that resolved path in the archive's entry map, so it throws ArchiveError(`Archive file '${normalizedPath}' not found`). Unlike error 3493 (unmaterializable link), here the lookup itself came up empty — the file simply is not in the archive (packages/utils/src/ar/reader.ts:139).

Source

Thrown at packages/utils/src/ar/reader.ts:139

		return [...children.values()].sort((left, right) =>
			left.name.toLowerCase().localeCompare(right.name.toLowerCase()),
		);
	}

	/** Extract one file member's bytes, following symlink aliases. */
	async readFile(subPath: string): Promise<ExtractedArchiveFile> {
		const normalizedPath = normalizeArchiveLookupPath(subPath);
		if (!normalizedPath) {
			throw new ArchiveError("Archive file path is required");
		}

		const resolvedPath = resolveArchiveLinkPath(this.#entries, normalizedPath, this.limits.maxLinkDepth);
		if (resolvedPath === "") {
			throw new ArchiveError(`Archive path '${normalizedPath}' is a directory`);
		}
		const entry = this.#entries.get(resolvedPath);
		if (!entry) {
			throw new ArchiveError(`Archive file '${normalizedPath}' not found`);
		}
		if (entry.isDirectory) {
			throw new ArchiveError(`Archive path '${normalizedPath}' is a directory`);
		}
		if (!entry.storage) {
			throw new ArchiveError(`Archive file '${normalizedPath}' has no readable storage`);
		}
		assertArchiveMemberSize(entry.size, normalizedPath, this.limits);

		if (entry.storage.type === "link") {
			throwUnreadableArchiveLink(entry.storage.targetPath, normalizedPath);
		}
		const bytes = await entry.storage.source.read(entry.size, normalizedPath);
		return {
			path: normalizedPath,
			isDirectory: false,
			size: entry.size,
			mtimeMs: entry.mtimeMs,

View on GitHub (pinned to 9690622007)

Solutions

  1. Dump the real entry list first: `reader.allEntries().map(e => e.path)` and confirm the exact stored path and casing.
  2. Normalize the path before lookup: forward slashes, no leading './' or '/', no trailing slash.
  3. Implement a case-insensitive fallback: find an entry whose lowercase path matches when the exact lookup fails.
  4. Catch ArchiveError with 'not found' and offer the user a fuzzy/closest-match list of archive entries.

Example fix

// before: brittle exact lookup
const file = await reader.readFile('Config.INI');

// after: exact, then case-insensitive fallback
const all = reader.allEntries();
const hit = all.find(e => e.path === 'Config.INI')
  ?? all.find(e => e.path.toLowerCase() === 'config.ini' && !e.isDirectory);
if (!hit) throw new Error(`File not in archive; available: ${all.map(e => e.path).join(', ')}`);
const file = await reader.readFile(hit.path);
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the exact stored path before reading
const all = reader.allEntries();
const wanted = 'Config.INI';
const hit = all.find(e => e.path === wanted) ?? all.find(e => !e.isDirectory && e.path.toLowerCase() === wanted.toLowerCase());
if (!hit) throw new Error(`File not in archive: ${wanted}`);
await reader.readFile(hit.path);

Type guard

function hasFile(entries: { path: string; isDirectory: boolean }[], p: string): boolean {
  return entries.some(e => !e.isDirectory && e.path === p);
}

Try / catch

try {
  return await reader.readFile(p);
} catch (err) {
  if (err instanceof ArchiveError && err.message.endsWith('not found')) {
    const suggestions = reader.allEntries()
      .filter(e => e.path.toLowerCase().includes(path.posix.basename(p).toLowerCase()))
      .map(e => e.path);
    throw new Error(`'${p}' not in archive. Close matches: ${suggestions.join(', ') || 'none'}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `reader.readFile('missing.txt')` with a path absent from the entry map; case mismatches ('Readme.md' vs 'readme.md'); separators or leading './' not matching stored entry paths; symlink resolving to a path that was never archived.

Common situations: Scripts hard-coding filenames that differ from the actual archive contents; archives regenerated with changed layout while consumers kept old paths; case-sensitivity surprises when archives made on case-insensitive systems are read case-sensitively; typos.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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