can1357/oh-my-pi · error · ToolError
path entry "${entry}" has an invalid selector ":${internalSp
Error message
path entry "${entry}" has an invalid selector ":${internalSplit.sel}" — use ":N-M" line ranges, ":raw"/":conflicts", a range plus ":raw", or percent-encode a literal ":" as %3A What it means
grep's parsePathSpecs accepts internal-URL-style selectors on path entries (via splitInternalUrlSel). If the selector does not match the read-selector grammar (:N-M ranges, :raw, :conflicts, range+:raw), the entry is rejected with guidance rather than silently widened or dropped.
Source
Thrown at packages/coding-agent/src/tools/grep.ts:172
const lower = sel.toLowerCase();
return lower === "raw" || lower === "conflicts" || parseLineRanges(sel) !== null;
}
async function parsePathSpecs(rawEntries: readonly string[], cwd: string): Promise<GrepPathSpec[]> {
const specs: GrepPathSpec[] = [];
for (const entry of rawEntries) {
// Internal URLs (`artifact://`, `skill://`, …) use the URL-aware splitter,
// which peels selector-shaped tails only for selector-capable schemes and
// leaves opaque ones (`mcp://`) intact. Unlike filesystem paths, their
// verbatim/index display modes (`raw`, `conflicts`) carry no meaning for
// content search, so we accept them — searching the whole resource — and
// still honor any embedded line range as a match filter.
const internalSplit = splitInternalUrlSel(entry);
if (internalSplit.sel !== undefined) {
// Reject selectors read's parseSel would reject (`:-10`, `:1-1:1-2`,
// `:conflicts:1-1`) instead of silently widening the search or dropping a chunk.
if (!isReadSelectorGrammar(internalSplit.sel)) {
throw new ToolError(
`path entry "${entry}" has an invalid selector ":${internalSplit.sel}" — use ":N-M" line ranges, ":raw"/":conflicts", a range plus ":raw", or percent-encode a literal ":" as %3A`,
);
}
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")`,View on GitHub (pinned to 9690622007)
Solutions
- Use only supported selectors: ':N-M' line ranges, ':raw', ':conflicts', or range plus ':raw'
- Percent-encode a literal colon in a filename as %3A
- Simplify to a plain path with at most a ':N-M' range
Example fix
// before
await grepTool.execute({ pattern: "foo", path: "src/my:file.txt" })
// after
await grepTool.execute({ pattern: "foo", path: "src/my%3Afile.txt" }) Defensive patterns
Strategy: validation
Validate before calling
function validateGrepPathEntry(entry: string): string {
const m = /^(.*?):([^:].*)$/.exec(entry);
if (m && !/^\d+-\d+$/.test(m[2]) && m[2] !== "raw" && m[2] !== "conflicts") {
// not a plain path, line range, raw, or conflicts -> encode literal colons
return entry.replace(/:/g, "%3A");
}
return entry;
} Try / catch
try {
return await grepTool.execute({ pattern, path: entries });
} catch (err) {
if (err instanceof ToolError && err.message.includes("invalid selector")) {
// retry with colons in literal filenames percent-encoded
return await grepTool.execute({ pattern, path: entries.map(e => e.replace(/:/g, "%3A")) });
}
throw err;
} Prevention
- Only use read-grammar selectors: :N-M, :raw, :conflicts, or range+:raw
- Percent-encode literal ':' in filenames as %3A
- Sanitize path lists built from other tools' output before passing to grep
When it happens
Trigger: A path entry like 'file.ts:conflicts:1-1', ': -10' style selectors, or any selector read's parseSel would reject; also a literal ':' in a filename that was not percent-encoded.
Common situations: Copy-pasting read-tool selectors into grep paths; filenames containing colons (e.g. 'my:file.txt') misinterpreted as selectors; hand-built path lists from other tools.
Related errors
- path entry "${entry}" — only line-range selectors like ":50-
- Line-range selector requires a single file, not a glob: ${en
- 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/fdc7b5177f42d60c.
Report an issue: GitHub.