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

  1. Verify the entry is a file first: check `isDirectory === false` on the matching entry from allEntries(), then readFile it.
  2. To read a whole folder, enumerate it with listDirectory(dir) and call readFile on each file child.
  3. If a trailing component is missing, append the actual file name to the path.
  4. 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

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


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