{"record":{"id":"fca06c55b20f3463","repo":"can1357/oh-my-pi","slug":"line-range-selector-requires-a-single-file-spec","errorCode":null,"errorMessage":"Line-range selector requires a single file: ${spec.original} is a directory","messagePattern":"Line-range selector requires a single file: (.+?) is a directory","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/grep.ts","lineNumber":1083,"sourceCode":"\t\t\t\t\t\tconst spec = pathSpecs[idx];\n\t\t\t\t\t\tif (!spec.ranges) continue;\n\t\t\t\t\t\tif (virtualInputIndexes.has(idx)) continue;\n\t\t\t\t\t\tconst resolved = internalResolution.resolvedPathsByInput[idx];\n\t\t\t\t\t\tif (!resolved) continue;\n\t\t\t\t\t\tconst materializedExternalPath = materializedExternalPaths.get(spec.clean);\n\t\t\t\t\t\tif (materializedExternalPath) {\n\t\t\t\t\t\t\tmergeRangesInto(rangesByAbsPath, path.resolve(materializedExternalPath), spec.ranges);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (resolved === spec.clean && !archiveDisplayMap.has(resolved)) {\n\t\t\t\t\t\t\t// Non-archive entry; ensure the cleaned path resolves to a regular file.\n\t\t\t\t\t\t\tconst absKey = path.resolve(resolveReadPath(resolved, this.session.cwd));\n\t\t\t\t\t\t\tconst stats = await stat(absKey).catch(() => null);\n\t\t\t\t\t\t\tif (!stats) {\n\t\t\t\t\t\t\t\tthrow new ToolError(`Path not found for line-range selector: ${spec.original}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!stats.isFile()) {\n\t\t\t\t\t\t\t\tthrow new ToolError(\n\t\t\t\t\t\t\t\t\t`Line-range selector requires a single file: ${spec.original} is a directory`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmergeRangesInto(rangesByAbsPath, absKey, spec.ranges);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmergeRangesInto(rangesByAbsPath, path.resolve(resolved), spec.ranges);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// When the only input was an archive selector, surface that selector instead\n\t\t\t\t\t// of the temp scratch path the resolver substituted in.\n\t\t\t\t\tconst physicalScopePath =\n\t\t\t\t\t\tsearchablePaths.length === 1 && archiveDisplayMap.get(searchPath)\n\t\t\t\t\t\t\t? (archiveDisplayMap.get(searchPath) as string)\n\t\t\t\t\t\t\t: scope.scopePath;\n\t\t\t\t\tscopePath = internalResolution.virtualScopePath\n\t\t\t\t\t\t? `${physicalScopePath}, ${internalResolution.virtualScopePath}`\n\t\t\t\t\t\t: physicalScopePath;\n\t\t\t\t} else {","sourceCodeStart":1065,"sourceCodeEnd":1101,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/grep.ts#L1065-L1101","documentation":"A line-range selector (`path:start-end`) must point at a single regular file because ranges are line-based. The stat succeeded but showed a directory, so the tool rejects it rather than silently ignoring the range.","triggerScenarios":"grep with path like `src/:10-20` or `src/components:1-100` — a directory path carrying line ranges.","commonSituations":"Mechanically appending :N-M to whatever path a variable holds; intending \"lines 10-20 of every file in the dir\", which this tool does not support via a range selector.","solutions":["Point the range selector at a specific file inside the directory (src/components/Button.tsx:10-20)","To search the directory, drop the range and use the `glob` option to filter files","Read the target file first to confirm it is a file, not a directory"],"exampleFix":"// before\nawait grep({ pattern: \"useState\", path: \"src/hooks:1-40\" });\n// after\nawait grep({ pattern: \"useState\", path: \"src/hooks/useAuth.ts:1-40\" });","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nconst [file] = spec.split(\":\");\nconst st = fs.statSync(file);\nif (st.isDirectory()) throw new Error(`Line ranges need a file, got directory: ${file}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only append :N-M to known file paths","Use the glob option for directory-wide filtering instead of range selectors"],"tags":["grep","line-range","directory"],"backgroundTag":"line-range-requires-file","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}