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

  1. Read the original message after 'Cannot read directory:' and fix the underlying cause (permissions, existence, mount).
  2. Check permissions (chmod/chown) or read with appropriate user.
  3. Target a specific subdirectory or file instead of the failing tree root.
  4. 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

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


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