can1357/oh-my-pi · error · ArchiveError
Archive path '${normalizedPath}' is a directory
Error message
Archive path '${normalizedPath}' is a directory What it means
`readFile` resolved the requested path and found a matching entry, but that entry is a directory — either directly, or because a symlink chain ended at a directory. It throws ArchiveError(`Archive path '${normalizedPath}' is a directory`) since byte extraction only applies to file members (packages/utils/src/ar/reader.ts:135).
Source
Thrown at packages/utils/src/ar/reader.ts:135
mode: childEntry?.mode ?? entry.mode,
});
}
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 {View on GitHub (pinned to 9690622007)
Solutions
- Verify the entry is a file first: check `isDirectory === false` on the matching entry from allEntries(), then readFile it.
- To read a whole folder, enumerate it with listDirectory(dir) and call readFile on each file child.
- If a trailing component is missing, append the actual file name to the path.
- Catch this ArchiveError and switch to directory listing rather than failing the whole operation.
Example fix
// before: assumes every path is a file
for (const p of paths) results.push(await reader.readFile(p));
// after: skip or expand directories
for (const p of paths) {
const e = reader.allEntries().find(x => x.path === p);
if (e?.isDirectory) {
for (const child of reader.listDirectory(p)) {
if (!child.isDirectory) results.push(await reader.readFile(child.path));
}
} else {
results.push(await reader.readFile(p));
}
} Defensive patterns
Strategy: type-guard
Validate before calling
// Only pass file entries to readFile
const entry = reader.allEntries().find(e => e.path === targetPath);
if (!entry || entry.isDirectory) {
throw new Error(`${targetPath} is not a file in the archive`);
}
await reader.readFile(targetPath); Type guard
function isFileEntry(
entries: { path: string; isDirectory: boolean }[],
p: string,
): entries is { path: string; isDirectory: boolean }[] & { fileFound: true } {
const e = entries.find(x => x.path === p);
return e !== undefined && !e.isDirectory;
} Try / catch
try {
return await reader.readFile(p);
} catch (err) {
if (err instanceof ArchiveError && err.message.endsWith('is a directory')) {
const children = reader.listDirectory(p);
logger.warn('Requested archive path is a directory; expand it explicitly', { p, children: children.length });
return null;
}
throw err;
} Prevention
- Check isDirectory before calling readFile; enumerate directories with listDirectory instead.
- Include an explicit file name in paths — never point readFile at a folder.
- For recursive extraction, walk listDirectory results and readFile only non-directory children.
- Handle symlink entries by checking the final resolved entry's isDirectory flag.
When it happens
Trigger: Calling `reader.readFile('src')` where 'src' is a directory entry; calling readFile on a symlink that resolves to a directory; passing a path without a filename; mixing up readFile and listDirectory targets.
Common situations: Recursive walkers that must read 'everything' and hit directory entries; user supplies a folder instead of a file path; archives where trailing-slash-less directory names look like files (e.g. entry 'data' being a folder).
Related errors
- Archive path '${normalizedPath}' is not a directory
- Archive hard link '${formatArchivePathForError(entry.path)}'
- Is a directory
- {}: hard link not allowed for directory
- archive destination exists: ${destSession}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3d34ec74e3a08a49.
Report an issue: GitHub.