{"record":{"id":"7fb9dbc0c9f3fdeb","repo":"can1357/oh-my-pi","slug":"line-range-selector-requires-a-single-file-not-a","errorCode":null,"errorMessage":"Line-range selector requires a single file, not a glob: ${entry}","messagePattern":"Line-range selector requires a single file, not a glob: (.+?)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/grep.ts","lineNumber":194,"sourceCode":"\t\t\tspecs.push({ original: entry, clean: internalSplit.path, ranges: selectorLineRanges(internalSplit.sel) });\n\t\t\tcontinue;\n\t\t}\n\t\t// Prefer a literal filesystem match when one exists — a real file named\n\t\t// `test:1-2` outranks the `:1-2` selector interpretation (issue #4618).\n\t\tconst strictSplit = splitPathAndSel(entry);\n\t\tconst split = await splitPathAndSelPreferringLiteral(entry, cwd);\n\t\tconst literalFilesystemMatch = strictSplit.sel !== undefined && split.sel === undefined;\n\t\tlet clean = literalFilesystemMatch ? resolveReadPath(entry, cwd) : entry;\n\t\tlet ranges: [LineRange, ...LineRange[]] | undefined;\n\t\tif (!literalFilesystemMatch && split.sel) {\n\t\t\tconst parsed = parseLineRanges(split.sel);\n\t\t\tif (!parsed) {\n\t\t\t\tthrow new ToolError(\n\t\t\t\t\t`path entry \"${entry}\" — only line-range selectors like \":50-100\" are supported (no \":raw\"/\":conflicts\")`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (hasGlobPathChars(split.path)) {\n\t\t\t\tthrow new ToolError(`Line-range selector requires a single file, not a glob: ${entry}`);\n\t\t\t}\n\t\t\tclean = split.path;\n\t\t\tranges = parsed;\n\t\t}\n\t\tspecs.push({\n\t\t\toriginal: entry,\n\t\t\tclean,\n\t\t\tliteralFilesystemMatch,\n\t\t\tranges,\n\t\t});\n\t}\n\treturn specs;\n}\n\nfunction mergeRangesInto(map: Map<string, LineRange[]>, absKey: string, ranges: readonly LineRange[]): void {\n\t// Concat-without-merge is correct: `isLineInRanges` scans linearly, so\n\t// duplicates/overlaps only cost a few extra comparisons per match.\n\tconst existing = map.get(absKey);","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/grep.ts#L176-L212","documentation":"A line-range selector like ':50-100' narrows matches to specific lines, which only makes sense for exactly one file. If the path part of the entry contains glob characters (* ? [ ] etc.), grep refuses to combine the two.","triggerScenarios":"Entries like 'src/**/*.ts:10-20' — a glob pattern with a line-range suffix.","commonSituations":"Trying to grep a line window across many files at once; appending ranges to an existing glob search string.","solutions":["Remove the line-range selector and grep the glob without it, or","Run separate grep calls per file with the ':N-M' selector","Filter matched lines yourself after a glob-wide search"],"exampleFix":"// before\nawait grepTool.execute({ pattern: \"foo\", path: \"src/**/*.ts:10-20\" })\n// after\nawait grepTool.execute({ pattern: \"foo\", path: \"src/specific.ts:10-20\" })","handlingStrategy":"validation","validationCode":"function validateLineRangeEntry(entry: string): string {\n  const idx = entry.lastIndexOf(\":\");\n  if (idx > 0 && /^\\d+(-\\d+)?$/.test(entry.slice(idx + 1))) {\n    const p = entry.slice(0, idx);\n    if (/[*?\\[]/.test(p)) throw new Error(`Line-range selector requires a single file: ${entry}`);\n  }\n  return entry;\n}","typeGuard":"function isGlobPath(p: string): boolean {\n  return /[*?\\[]/.test(p);\n}","tryCatchPattern":"try {\n  return await grepTool.execute({ pattern, path: entry });\n} catch (err) {\n  if (err instanceof ToolError && err.message.startsWith(\"Line-range selector requires a single file\")) {\n    return await grepTool.execute({ pattern, path: entry.split(\":\").slice(0, -1).join(\":\") });\n  }\n  throw err;\n}","preventionTips":["Never combine glob wildcards with :N-M selectors","Expand the glob first, then issue per-file ranged greps if line windows are needed","Keep ranged searches to one explicit file path"],"tags":["parsing","selector-syntax","glob-tool","grep-tool"],"backgroundTag":"invalid-path-selector","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}