can1357/oh-my-pi · error · ToolError

grep cannot recurse the directory listing at ${rawPath}; gre

Error message

grep cannot recurse the directory listing at ${rawPath}; grep a specific file under it (e.g. ${rawPath.replace(/\/+$/, "")}/<file>) or read ${rawPath} to list its entries

What it means

The resolved internal resource is a directory whose listing has no backing local path (sourcePath is unset, e.g. a remote ssh:// directory). grep would otherwise search the rendered listing text, which is misleading, so it refuses and tells you to grep a specific file or read the URL to list entries.

Source

Thrown at packages/coding-agent/src/tools/grep.ts:817

	};

	for (let idx = 0; idx < paths.length; idx++) {
		const rawPath = paths[idx];
		if (!rawPath || opts.archiveDisplayMap.has(rawPath) || !internalRouter.canHandle(rawPath)) {
			continue;
		}
		// `ssh://[::1]/path` carries `[`/`]` in the IPv6 authority — glob metacharacters
		// — so check only the path portion for ssh:// (the SSH handler reads a single
		// remote file; there is no glob expansion). A glob in the remote path still trips.
		const globTarget = /^ssh:\/\//i.test(rawPath) ? rawPath.replace(/^ssh:\/\/[^/]*/i, "") : rawPath;
		if (hasGlobPathChars(globTarget)) {
			throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
		}
		let resource = await internalRouter.resolve(rawPath, context);
		// A directory listing with no backing local path (e.g. a remote ssh:// dir)
		// has no real contents to grep — searching its listing text would be
		// misleading. Local/skill/vault dir resources set `sourcePath` and skip this.
		if (resource.isDirectory && !resource.sourcePath) {
			throw new ToolError(
				`grep cannot recurse the directory listing at ${rawPath}; grep a specific file under it (e.g. ${rawPath.replace(/\/+$/, "")}/<file>) or read ${rawPath} to list its entries`,
			);
		}
		if (resource.sourcePath) {
			paths[idx] = resource.sourcePath;
			if (resource.immutable) {
				immutableSourcePaths.add(path.resolve(resource.sourcePath));
			}
			continue;
		}

		// No sourcePath: this handler needs its content materialized so the
		// virtual expansion can search it. Re-resolve without pathOnly.
		if (context.pathOnly) {
			resource = await internalRouter.resolve(rawPath, { ...context, pathOnly: false });
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the directory URL first to enumerate entries, then grep a specific file under it
  2. Append a concrete file name to the directory URL (e.g. ssh://host/dir/file.log)
  3. If the files are also mounted locally, grep the local filesystem path instead

Example fix

// before
await grep({ pattern: "ERROR", path: "ssh://host/var/log/" });
// after
const listing = await read("ssh://host/var/log/");
await grep({ pattern: "ERROR", path: "ssh://host/var/log/syslog" });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await grep({ pattern, path: dirUrl });
} catch (err) {
  if (err instanceof ToolError && /cannot recurse the directory listing/.test(err.message)) {
    const listing = await read(dirUrl);
    // pick a concrete file from listing and grep it
  } else throw err;
}

Prevention

When it happens

Trigger: grep with `path` set to an internal directory URL without a local backing store, e.g. `internal://remote/dir/` or `ssh://host/var/data/` where the handler returns a listing only.

Common situations: Pointing grep at a remote ssh:// directory expecting recursive search over remote files; local, skill, and vault directories set sourcePath and do NOT trigger this — only listing-only virtual dirs do.

Related errors


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