can1357/oh-my-pi · error

vault:// path resolution only supports plain filesystem path

Error message

vault:// path resolution only supports plain filesystem paths

What it means

`resolveVaultUrlToPath` maps a vault:// URL to a plain filesystem path, which is only defined for `fs-file` and `fs-dir` parse results. Op-based URLs (`file-op`, `vault-op`), `list-vaults`, and `vault-info` have no single filesystem equivalent, so the resolver rejects them with this error. Use the protocol handler's resolve() for those kinds instead.

Source

Thrown at packages/coding-agent/src/internal-urls/vault-protocol.ts:427

	let current = targetPath;
	while (true) {
		ensureWithinRoot(current, rootPath);
		try {
			return fs.realpathSync(current);
		} catch (error) {
			if (!isEnoent(error)) throw error;
			const parent = path.dirname(current);
			if (parent === current) throw error;
			current = parent;
		}
	}
}

export function resolveVaultUrlToPath(input: string | InternalUrl): string {
	if (!isVaultEnabled()) throw new VaultDisabledError();
	const parsed = parseVaultUrl(input);
	if (parsed.kind !== "fs-file" && parsed.kind !== "fs-dir") {
		throw new Error("vault:// path resolution only supports plain filesystem paths");
	}

	const cachedRoot = getCachedVaultRoot(parsed.ref);
	if (!cachedRoot) {
		throw new Error(
			"vault:// path resolution requires a cached vault root; read vault:// first or use the write tool",
		);
	}

	const resolvedRoot = fs.realpathSync(cachedRoot);
	const targetPath = parsed.relativePath ? path.resolve(resolvedRoot, parsed.relativePath) : resolvedRoot;
	ensureWithinRoot(targetPath, resolvedRoot);

	try {
		const realTarget = fs.realpathSync(targetPath);
		ensureWithinRoot(realTarget, resolvedRoot);
	} catch (error) {
		if (!isEnoent(error)) throw error;

View on GitHub (pinned to 9690622007)

Solutions

  1. Strip the `op` query parameter and resolve the underlying path portion of the URL.
  2. For op/vault-info/list URLs, use VaultProtocolHandler.resolve() to get an InternalResource instead of a filesystem path.
  3. Guard the call: parse the URL first with parseVaultUrl and only call resolveVaultUrlToPath when kind is fs-file or fs-dir.

Example fix

// before
const p = resolveVaultUrlToPath("vault://_/notes/idea.md?op=backlinks"); // throws
// after
const parsed = parseVaultUrl("vault://_/notes/idea.md?op=backlinks");
const p = parsed.kind === "fs-file" || parsed.kind === "fs-dir"
  ? resolveVaultUrlToPath(parsed)
  : null; // handle ops via handler.resolve() instead
Defensive patterns

Strategy: type-guard

Validate before calling

import { parseVaultUrl } from "./internal-urls/vault-protocol";
const parsed = parseVaultUrl(url);
if (parsed.kind !== "fs-file" && parsed.kind !== "fs-dir") {
  throw new Error(`cannot resolve ${parsed.kind} to a filesystem path`);
}
const p = resolveVaultUrlToPath(parsed);

Type guard

function isPlainFsVaultUrl(parsed: ReturnType<typeof parseVaultUrl>): parsed is Extract<ReturnType<typeof parseVaultUrl>, { kind: "fs-file" | "fs-dir" }> {
  return parsed.kind === "fs-file" || parsed.kind === "fs-dir";
}

Try / catch

try {
  const p = resolveVaultUrlToPath(url);
} catch (err) {
  if (err instanceof Error && err.message === "vault:// path resolution only supports plain filesystem paths") {
    // route op/info/list URLs through handler.resolve() instead
  } else throw err;
}

Prevention

When it happens

Trigger: resolveVaultUrlToPath("vault://_/notes/idea.md?op=backlinks") (file-op), resolveVaultUrlToPath("vault://_/?op=search&q=x") (vault-op), resolveVaultUrlToPath("vault://_") (vault-info), or resolveVaultUrlToPath("vault://") (list-vaults).

Common situations: Passing arbitrary vault:// links (including op links embedded in notes) to a helper that expects plain note/folder paths; code that assumed every vault URL corresponds to a file on disk.

Related errors


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