can1357/oh-my-pi · error

local:// URL must resolve to a file or directory: ${url.href

Error message

local:// URL must resolve to a file or directory: ${url.href}

What it means

After successful resolution and stat, resolveLocalTarget only accepts directories and regular files. If the target is another node type — FIFO, socket, device file, symlink resolved to something unusual — it throws this error because the resource contract (content reading, sizes) only supports files and directories.

Source

Thrown at packages/coding-agent/src/internal-urls/local-protocol.ts:391

	let realTargetPath: string;
	try {
		realTargetPath = await fs.realpath(targetPath);
	} catch (error) {
		if (isEnoent(error)) {
			throw new Error(`Local file not found: ${url.href}`);
		}
		throw error;
	}

	ensureWithinRoot(realTargetPath, resolvedRoot);

	const stat = await fs.stat(realTargetPath);
	if (stat.isDirectory()) {
		return { kind: "directory", path: realTargetPath };
	}
	if (!stat.isFile()) {
		throw new Error(`local:// URL must resolve to a file or directory: ${url.href}`);
	}
	return { kind: "file", path: realTargetPath, size: stat.size };
}

/**
 * Resolve a local:// URL to a regular on-disk file, applying the same
 * realpath + containment guarantees as {@link LocalProtocolHandler.resolve}
 * but WITHOUT reading or UTF-8-decoding its contents. Returns null when there
 * is no active session or when the URL targets the root listing or a directory;
 * throws the handler's not-found and "escapes local root" errors for missing
 * files and symlink escapes.
 *
 * Options are resolved via {@link LocalProtocolHandler.resolveOptions} so the
 * caller-options → override → registry order matches router resolution exactly.
 * The read tool uses this to detect and emit image files from their real path
 * before the text-only resource contract would decode the binary into mojibake.
 */
export async function resolveLocalUrlToFile(

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the non-regular file from the local root and use a regular file instead.
  2. Redirect the tool that created the FIFO/socket to a different directory (e.g. /tmp) outside the session artifacts.
  3. Read device/special files with the standard filesystem tools if genuinely needed, not local://.
Defensive patterns

Strategy: validation

Validate before calling

const st = await fs.stat(resolvedPath);
if (!st.isFile() && !st.isDirectory()) throw new Error(`local:// target must be a regular file or directory, got mode ${st.mode}`);

Try / catch

try { resource = await handler.resolve(url, ctx); } catch (e) { if (String(e.message).includes('must resolve to a file or directory')) { /* inspect the target's file type with stat */ } else throw e; }

Prevention

When it happens

Trigger: Creating a FIFO or unix socket inside the session local root (e.g. mkfifo local-root/pipe) and then resolving local://pipe; a symlink whose realpath target is a device node like /dev/null placed within the root.

Common situations: Scripts or build tools that create named pipes or sockets in the artifacts directory; tests that stub the local root with special files.

Related errors


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