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
- Dump the real entry list first: `reader.allEntries().map(e => e.path)` and confirm the exact stored path and casing.
- Normalize the path before lookup: forward slashes, no leading './' or '/', no trailing slash.
- Implement a case-insensitive fallback: find an entry whose lowercase path matches when the exact lookup fails.
- 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
- Source paths from allEntries() listings, not hard-coded literals.
- Match stored casing exactly; add a case-insensitive fallback for robustness.
- Normalize paths (forward slashes, no './' or leading '/') before lookup.
- Re-validate hard-coded paths whenever archives are regenerated with a new layout.
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
- Archive path '${normalizedPath}' not found
- Path not found: ${partition.missing.join(", ")}
- Archive path '${normalizedPath}' is not a directory
- cannot access {}: Not a directory
- invalid template, {}; with --tmpdir, it may not be absolute
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6ed78118f2e23e50.
Report an issue: GitHub.