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

  1. Strip the scheme and resolve the underlying path, or route the value through the `local://` protocol handler instead of resolvePath.
  2. Convert stored `local://` references to plain absolute/relative filesystem paths in your config before they reach resolvePath.
  3. 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.
  4. 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

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


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