can1357/oh-my-pi · error · Error
Path "${filePath}" uses internal scheme "local://" and must
Error message
Path "${filePath}" uses internal scheme "local://" and must be resolved through the proper protocol handler, not as a filesystem path. What it means
resolvePath turns user-facing path strings into filesystem paths (~ expansion, cwd-relative resolution) but `local://` is an internal opencode scheme handled by dedicated protocol handlers, not a real filesystem path. If, after expandPath and normalizeLocalScheme, the string still starts with `local://`, the caller is using the wrong resolver — resolving it as a file would silently produce a bogus path like `cwd/local://...`. The throw is a fail-fast guard against that.
Source
Thrown at packages/coding-agent/src/extensibility/utils.ts:17
import * as path from "node:path";
import { postmortem } from "@oh-my-pi/pi-utils";
import { theme } from "../modes/theme/theme";
import { expandPath, normalizeLocalScheme } from "../tools/path-utils";
import type { HookUIContext } from "./hooks/types";
/**
* Resolve a file path:
* - Absolute paths used as-is
* - Paths starting with ~ expanded to home directory
* - Relative paths resolved from cwd
*/
export function resolvePath(filePath: string, cwd: string): string {
const expanded = expandPath(filePath);
const expandedAndNormalized = normalizeLocalScheme(expanded);
if (expandedAndNormalized.startsWith("local://")) {
throw new Error(
`Path "${filePath}" uses internal scheme "local://" and must be resolved through the proper protocol handler, not as a filesystem path.`,
);
}
if (path.isAbsolute(expanded)) {
return expanded;
}
return path.resolve(cwd, expanded);
}
/**
* Create a no-op UI context for headless modes.
*/
export function createNoOpUIContext(): HookUIContext {
return {
select: async () => undefined,
confirm: async () => false,
input: async () => undefined,
notify: () => {},View on GitHub (pinned to 9690622007)
Solutions
- Strip the scheme and resolve the underlying path, or route the value through the `local://` protocol handler instead of resolvePath.
- Convert stored `local://` references to plain absolute/relative filesystem paths in your config before they reach resolvePath.
- If you only need the path portion, remove the prefix (e.g. `p.replace(/^local:\/\//, "")`) and pass the result — but prefer the proper protocol API.
- Check upstream producers of the string: they should emit plain paths when the consumer is a filesystem resolver.
Example fix
// before
const abs = resolvePath(config.filePath, cwd); // config.filePath = "local:///repo/src/a.ts"
// after
const abs = config.filePath.startsWith("local://")
? resolvePath(config.filePath.slice("local://".length), cwd)
: resolvePath(config.filePath, cwd); Defensive patterns
Strategy: validation
Validate before calling
function isFilesystemPath(p: string): boolean {
return !p.startsWith("local://");
}
if (!isFilesystemPath(filePath)) {
// route through the protocol handler instead of resolvePath
} Type guard
function isLocalSchemeUri(v: string): boolean {
return v.startsWith("local://");
}
// usage: if (isLocalSchemeUri(p)) handleViaProtocol(p); else resolvePath(p, cwd); Try / catch
let abs: string;
try {
abs = resolvePath(filePath, cwd);
} catch (err) {
if (err instanceof Error && err.message.includes('internal scheme "local://"')) {
abs = resolveLocalSchemeThroughHandler(filePath); // proper protocol path
} else throw err;
} Prevention
- Store plain filesystem paths in configs consumed by resolvePath; reserve `local://` for internal resource references.
- When passing values between subsystems, keep track of whether a value is a URI or a path and use the matching resolver.
- Check inputs for a scheme prefix before path resolution and route accordingly.
- Never construct `local://` strings by hand; obtain them only from APIs that issue them.
When it happens
Trigger: Calling resolvePath with a path string of the form `local://...` — e.g. a plugin/hook config or tool argument that stores a `local://` URI, code passing a previously stored internal resource reference back into resolvePath, or a `local://` prefix that normalizeLocalScheme did not consume (e.g. doubled or malformed scheme).
Common situations: A user pastes `local:///home/me/project/file.ts` into a plugin config where a plain path is expected; an extension round-trips an internal URI through a file-path API; a hook records `local://` references in config after an editor interaction and the plugin later tries to resolve them as disk paths.
Related errors
- Protocol paths are not supported by this helper: #{path}
- project-scoped install requires running inside a project dir
- cannot move {0} to a subdirectory of itself, {1}
- No model configured
- Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/663395bb450c280c.
Report an issue: GitHub.