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
- Read the directory URL first to enumerate entries, then grep a specific file under it
- Append a concrete file name to the directory URL (e.g. ssh://host/dir/file.log)
- 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
- read the directory URL first to enumerate entries before grepping it
- Prefer local/skill/vault dirs (they expose sourcePath and are greppable) over listing-only remote dirs
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
- Glob patterns are not supported for internal URLs: ${rawPath
- Line-range selector requires a single file: ${spec.original}
- Is a directory
- {}: hard link not allowed for directory
- Directory paths are not supported by read(): ${filePath}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/642f8f68357e5dba.
Report an issue: GitHub.