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

  1. Use only supported selectors: ':N-M' line ranges, ':raw', ':conflicts', or range plus ':raw'
  2. Percent-encode a literal colon in a filename as %3A
  3. 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

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


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