can1357/oh-my-pi · error

Invalid URL encoding in vault:// path: ${url.href}

Error message

Invalid URL encoding in vault:// path: ${url.href}

What it means

`decodeVaultPath` decodes the vault:// URL pathname with `decodeURIComponent` after normalizing backslashes to slashes. If the percent-encoding is malformed (e.g. a stray `%` not followed by two hex digits, or a truncated escape), `decodeURIComponent` throws a URIError which is rethrown as this descriptive error including the full URL href. This happens before relative-path validation, so it is purely about URL syntax.

Source

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

function decodeVaultPath(url: InternalUrl): {
	rawPathname: string;
	relativePath: string;
	hasPath: boolean;
	isDirectory: boolean;
} {
	const rawPathname = url.rawPathname ?? url.pathname;
	const hasPath = rawPathname !== undefined && rawPathname !== "" && rawPathname !== "/";
	const isDirectory = rawPathname === "/" || rawPathname.endsWith("/");
	if (!hasPath) {
		return { rawPathname, relativePath: "", hasPath: false, isDirectory };
	}

	let decoded: string;
	try {
		decoded = decodeURIComponent(rawPathname.slice(1).replaceAll("\\", "/"));
	} catch {
		throw new Error(`Invalid URL encoding in vault:// path: ${url.href}`);
	}

	try {
		validateRelativePath(decoded);
	} catch (error) {
		throw toVaultValidationError(error);
	}

	return { rawPathname, relativePath: decoded.replace(/\/+$/, ""), hasPath: true, isDirectory };
}

function paramsFromUrl(url: InternalUrl): VaultParams {
	const params: VaultParams = {};
	for (const [key, value] of url.searchParams) {
		params[key] = value === "" ? true : value;
	}
	return params;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Percent-encode all `%` characters in the path as `%25` (use encodeURIComponent per path segment).
  2. Regenerate the URL instead of hand-editing it; use the encodeRelativePath-style scheme (encodeURIComponent each segment joined by `/`).
  3. Rename the file so its name contains no raw `%` characters, then use the encoded form.

Example fix

// before
const url = `vault://_/notes/${name}.md`; // name = "100% done"
// after
const url = `vault://_/notes/${encodeURIComponent(name)}.md`; // "100%25 done"
Defensive patterns

Strategy: validation

Validate before calling

function isWellFormedPercentEncoding(s: string): boolean {
  // every '%' must be followed by two hex digits
  return !/(^|[^%])%([^0-9A-Fa-f]{2}|$)|%$/.test(s) || !/%(?![0-9A-Fa-f]{2})/.test(s);
}
// equivalently: decodeURIComponent(test) inside try/catch before building the URL

Type guard

function hasValidEncoding(rawPathname: string): boolean {
  try { decodeURIComponent(rawPathname.replace(/\\/g, "/").slice(1)); return true; }
  catch { return false; }
}

Try / catch

try {
  const res = await handler.resolve(parseInternalUrl(url));
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Invalid URL encoding in vault://")) {
    url = reEncodeUrl(url); // encodeURIComponent each path segment
  } else throw err;
}

Prevention

When it happens

Trigger: parseVaultUrl -> decodeVaultPath is called with a URL like `vault://_/notes/100%.md`, `vault://_/a%2b%zz.txt`, or a truncated copy-pasted URL ending in `%e`. Any `%` that is not part of a valid `%XX` escape triggers it.

Common situations: Pasting Obsidian URIs or file names containing literal `%` characters unencoded into a vault:// URL; hand-building URLs with string concatenation instead of encodeURIComponent; truncating a URL mid-escape when editing it.

Related errors


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