can1357/oh-my-pi · error · ToolError

Path not found for line-range selector: ${spec.original}

Error message

Path not found for line-range selector: ${spec.original}

What it means

A line-range selector (e.g. `file.ts:10-20` as the grep path) resolved to a path that does not exist on disk. Before applying ranges, the tool stats the cleaned path and throws this if the stat fails, differentiating "path not found" from the directory case that follows.

Source

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

					// archive entries are keyed by scratch path, URL entries by read-cache
					// content path, and ordinary files by their resolved filesystem path.
					for (let idx = 0; idx < pathSpecs.length; idx++) {
						const spec = pathSpecs[idx];
						if (!spec.ranges) continue;
						if (virtualInputIndexes.has(idx)) continue;
						const resolved = internalResolution.resolvedPathsByInput[idx];
						if (!resolved) continue;
						const materializedExternalPath = materializedExternalPaths.get(spec.clean);
						if (materializedExternalPath) {
							mergeRangesInto(rangesByAbsPath, path.resolve(materializedExternalPath), spec.ranges);
							continue;
						}
						if (resolved === spec.clean && !archiveDisplayMap.has(resolved)) {
							// Non-archive entry; ensure the cleaned path resolves to a regular file.
							const absKey = path.resolve(resolveReadPath(resolved, this.session.cwd));
							const stats = await stat(absKey).catch(() => null);
							if (!stats) {
								throw new ToolError(`Path not found for line-range selector: ${spec.original}`);
							}
							if (!stats.isFile()) {
								throw new ToolError(
									`Line-range selector requires a single file: ${spec.original} is a directory`,
								);
							}
							mergeRangesInto(rangesByAbsPath, absKey, spec.ranges);
						} else {
							mergeRangesInto(rangesByAbsPath, path.resolve(resolved), spec.ranges);
						}
					}
					// When the only input was an archive selector, surface that selector instead
					// of the temp scratch path the resolver substituted in.
					const physicalScopePath =
						searchablePaths.length === 1 && archiveDisplayMap.get(searchPath)
							? (archiveDisplayMap.get(searchPath) as string)
							: scope.scopePath;
					scopePath = internalResolution.virtualScopePath

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the file path exists relative to the session cwd (ls or read the file first)
  2. Remove the :ranges suffix and grep the directory to confirm the correct file name
  3. Fix the working directory or use an absolute path in the selector

Example fix

// before
await grep({ pattern: "TODO", path: "src/ap.ts:1-50" });
// after
await grep({ pattern: "TODO", path: "src/app.ts:1-50" });
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs";
const [file] = spec.split(":");
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`No such file: ${file}`);

Prevention

When it happens

Trigger: grep with a path like `src/app.ts:5-10` where src/app.ts does not exist (typo, wrong cwd, file deleted/moved, wrong branch checked out).

Common situations: See trigger scenarios.

Related errors


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