can1357/oh-my-pi · error · ToolError
Line-range selector requires a single file, not a glob: ${en
Error message
Line-range selector requires a single file, not a glob: ${entry} What it means
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.
Source
Thrown at packages/coding-agent/src/tools/grep.ts:194
specs.push({ original: entry, clean: internalSplit.path, ranges: selectorLineRanges(internalSplit.sel) });
continue;
}
// Prefer a literal filesystem match when one exists — a real file named
// `test:1-2` outranks the `:1-2` selector interpretation (issue #4618).
const strictSplit = splitPathAndSel(entry);
const split = await splitPathAndSelPreferringLiteral(entry, cwd);
const literalFilesystemMatch = strictSplit.sel !== undefined && split.sel === undefined;
let clean = literalFilesystemMatch ? resolveReadPath(entry, cwd) : entry;
let ranges: [LineRange, ...LineRange[]] | undefined;
if (!literalFilesystemMatch && split.sel) {
const parsed = parseLineRanges(split.sel);
if (!parsed) {
throw new ToolError(
`path entry "${entry}" — only line-range selectors like ":50-100" are supported (no ":raw"/":conflicts")`,
);
}
if (hasGlobPathChars(split.path)) {
throw new ToolError(`Line-range selector requires a single file, not a glob: ${entry}`);
}
clean = split.path;
ranges = parsed;
}
specs.push({
original: entry,
clean,
literalFilesystemMatch,
ranges,
});
}
return specs;
}
function mergeRangesInto(map: Map<string, LineRange[]>, absKey: string, ranges: readonly LineRange[]): void {
// Concat-without-merge is correct: `isLineInRanges` scans linearly, so
// duplicates/overlaps only cost a few extra comparisons per match.
const existing = map.get(absKey);View on GitHub (pinned to 9690622007)
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
Example fix
// before
await grepTool.execute({ pattern: "foo", path: "src/**/*.ts:10-20" })
// after
await grepTool.execute({ pattern: "foo", path: "src/specific.ts:10-20" }) Defensive patterns
Strategy: validation
Validate before calling
function validateLineRangeEntry(entry: string): string {
const idx = entry.lastIndexOf(":");
if (idx > 0 && /^\d+(-\d+)?$/.test(entry.slice(idx + 1))) {
const p = entry.slice(0, idx);
if (/[*?\[]/.test(p)) throw new Error(`Line-range selector requires a single file: ${entry}`);
}
return entry;
} Type guard
function isGlobPath(p: string): boolean {
return /[*?\[]/.test(p);
} Try / catch
try {
return await grepTool.execute({ pattern, path: entry });
} catch (err) {
if (err instanceof ToolError && err.message.startsWith("Line-range selector requires a single file")) {
return await grepTool.execute({ pattern, path: entry.split(":").slice(0, -1).join(":") });
}
throw err;
} Prevention
- 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
When it happens
Trigger: Entries like 'src/**/*.ts:10-20' — a glob pattern with a line-range suffix.
Common situations: Trying to grep a line window across many files at once; appending ranges to an existing glob search string.
Related errors
- path entry "${entry}" has an invalid selector ":${internalSp
- path entry "${entry}" — only line-range selectors like ":50-
- Invalid regex: ${message.replace(/^Invalid regular expressio
- Replacement text is not valid UTF-8: {err}
- V2 compaction stream parse failed: ${err instanceof Error ?
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7fb9dbc0c9f3fdeb.
Report an issue: GitHub.