can1357/oh-my-pi · error · ToolError

Cannot ${internalUrlAction} a remote ssh:// path (no local f

Error message

Cannot ${internalUrlAction} a remote ssh:// path (no local file): ${rawPath}. Use \`read ${rawPath}\` to view it, or use \`grep\` on a specific remote file.

What it means

ssh:// URLs are recognized by the internal URL router but represent remote files with no local backing, so search-scope resolution rejects them explicitly. The message directs you to `read` (which can render remote content) or grep on a specific remote file path.

Source

Thrown at packages/coding-agent/src/tools/path-utils.ts:1547

				resolvedPathInputs.push(resolved.sourcePath);
				if (opts.trackImmutableSources && resolved.immutable) {
					immutableSourcePaths.add(path.resolve(resolved.sourcePath));
				}
				continue;
			}
			// Resolver missing or declined (e.g. ftp/ws/wss): fail explicitly
			// instead of letting the local-path fallthrough surface a confusing
			// "Path not found" for a URL-shaped input.
			throw new ToolError(
				`Cannot ${internalUrlAction} external URL: ${rawPath}. Use \`read\` to fetch web content, then search the returned text.`,
			);
		}
		if (!internalRouter.canHandle(rawPath)) {
			resolvedPathInputs.push(rawPath);
			continue;
		}
		if (isSshUrl(rawPath)) {
			throw new ToolError(
				`Cannot ${internalUrlAction} a remote ssh:// path (no local file): ${rawPath}. Use \`read ${rawPath}\` to view it, or use \`grep\` on a specific remote file.`,
			);
		}
		if (hasGlobPathChars(rawPath)) {
			throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
		}
		const resource = await internalRouter.resolve(rawPath, {
			cwd,
			settings: opts.settings,
			signal: opts.signal,
			sessionFile: opts.sessionFile,
			localProtocolOptions: opts.localProtocolOptions,
			skills: opts.skills,
			// Tool-scope resolution only needs `sourcePath`; skip content
			// materialization so large artifacts (or any handler that separates
			// path from content) stay searchable without OOM risk.
			pathOnly: true,
		});

View on GitHub (pinned to 9690622007)

Solutions

  1. Use `read ssh://host/repo/src` to view the remote content instead of searching it.
  2. Run grep against a specific remote file path rather than a directory-level search scope.
  3. Clone or mount the remote repository locally, then search the local path.
  4. If the ssh URL maps to a locally checked-out workspace, reference the local path instead.

Example fix

// before
search({ pattern: "FIXME", paths: ["ssh://dev@box/srv/app"] })
// after
await read({ path: "ssh://dev@box/srv/app/src/main.ts" });
// or: git clone ssh://dev@box/srv/app && search({ paths: ["./app"] })
Defensive patterns

Strategy: validation

Validate before calling

if (p.startsWith("ssh://")) {
  throw new Error("ssh:// paths cannot back a search scope; use read or clone locally");
}

Type guard

const isSshPath = (p) => p.startsWith("ssh://");

Try / catch

try { scope = await resolveToolSearchScope(opts); }
catch (e) {
  if (e.message.includes("remote ssh:// path")) {
    return read({ path: p }); // degrade to viewing the remote resource
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a scope entry like `ssh://host/repo/src` to search/ast_grep/ast_edit via resolveToolSearchScope where isSshUrl(rawPath) is true — the router canHandle it but there is no local file to search.

Common situations: Agent or user points a bulk search at a remote SSH path expecting it to behave like a local directory; mixing remote repo references into an otherwise local path list.

Related errors


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