can1357/oh-my-pi · error · ToolError
Cannot read directory: ${message}
Error message
Cannot read directory: ${message} What it means
Directory reads render a tree via a tree-rendering helper; any error it raises (permission denied, IO error, symlink loops, etc.) is caught and re-thrown as a single ToolError prefixed with 'Cannot read directory:' keeping the original message.
Source
Thrown at packages/coding-agent/src/tools/read.ts:2333
signal?: AbortSignal,
): Promise<AgentToolResult<ReadToolDetails>> {
const READ_DIRECTORY_MAX_DEPTH = 2;
const READ_DIRECTORY_CHILD_LIMIT = 12;
throwIfAborted(signal);
let tree: DirectoryTree;
try {
tree = await buildDirectoryTree(absolutePath, {
maxDepth: READ_DIRECTORY_MAX_DEPTH,
perDirLimit: READ_DIRECTORY_CHILD_LIMIT,
rootLimit: null,
// `lineCap` truncates the rendered tree itself, so apply it only when the caller
// did not request an offset — otherwise we'd cap the first N lines before slicing.
lineCap: offset === undefined && limit !== undefined ? limit : null,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new ToolError(`Cannot read directory: ${message}`);
}
throwIfAborted(signal);
const output = tree.totalLines <= 1 ? "(empty directory)" : tree.rendered;
const details: ReadToolDetails = {
isDirectory: true,
resolvedPath: tree.rootPath,
};
// Slice the rendered listing when the caller passed an offset/limit. We do this
// instead of passing the selector down to `buildDirectoryTree` because the tree
// builder lays out entries hierarchically (per-dir caps, recent-then-elided
// summaries); line-based slicing operates on the formatted text and matches what
// users expect from `:N-M` on long listings.
const wantsSlice = offset !== undefined || limit !== undefined;
if (wantsSlice) {
const allLines = output.split("\n");
const start = offset ? Math.max(0, offset - 1) : 0;View on GitHub (pinned to 9690622007)
Solutions
- Read the original message after 'Cannot read directory:' and fix the underlying cause (permissions, existence, mount).
- Check permissions (chmod/chown) or read with appropriate user.
- Target a specific subdirectory or file instead of the failing tree root.
- Retry if the failure was a transient deletion race.
Example fix
// before
read("/root/secure/") // EACCES
// after
read("/home/user/project/") // accessible directory Defensive patterns
Strategy: try-catch
Validate before calling
try { fs.accessSync(dir, fs.constants.R_OK | fs.constants.X_OK); } catch { throw new Error('No read access to directory: ' + dir); } Type guard
null
Try / catch
try { return await read(dir) } catch (e) { if (e instanceof ToolError && e.message.startsWith('Cannot read directory:')) { const cause = e.message.slice('Cannot read directory:'.length).trim(); /* inspect cause: EACCES, ENOENT, etc. */ } throw e; } Prevention
- Check read/execute permissions on directories before listing
- Avoid reading directories that external processes may mutate concurrently
- Prefer targeted file reads over deep tree walks of huge or networked trees
When it happens
Trigger: read() on a directory where the underlying tree walk/render throws: unreadable subdirectory (EACCES), deleted mid-scan, too many levels, or OS-level IO failure.
Common situations: Reading directories without execute permission on Linux, racing with an external process removing files, network mounts that time out, or very deep node_modules trees.
Related errors
- Is a directory
- {}: {error}
- {}: hard link not allowed for directory
- inter-device move failed: {} to {}; unable to remove target:
- Permission denied
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/94e6eaf33cfa0756.
Report an issue: GitHub.