can1357/oh-my-pi · error

vault:// write only supports plain file paths

Error message

vault:// write only supports plain file paths

What it means

After the enabled check, VaultProtocolHandler.write() parses the URL with parseVaultUrl() and requires the result to have kind "fs-file" — i.e. a plain filesystem path inside a vault. Other parsed kinds (directory or CLI-backed references like attachments) are rejected because writing arbitrary content only makes sense for plain file paths. This is an input-validation error, not a state error.

Source

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

			case "list-vaults":
				return this.#listVaults(parsed, context);
			case "vault-info":
				return this.#vaultInfo(parsed, context);
			case "fs-dir":
				return this.#listDir(parsed, context);
			case "fs-file":
				return this.#readFile(parsed, context);
			case "file-op":
			case "vault-op":
				return this.#runCli(parsed, context);
		}
	}

	async write(url: InternalUrl, content: string, context?: WriteContext): Promise<void> {
		if (!isVaultEnabled()) throw new VaultDisabledError();
		const parsed = parseVaultUrl(url);
		if (parsed.kind !== "fs-file") {
			throw new Error("vault:// write only supports plain file paths");
		}
		await this.#writeFile(parsed, content, context);
	}

	async #spawn(args: string[], context?: ResolveContext | WriteContext): Promise<ObsidianSpawnResult> {
		const bin = requireObsidianBinary(this.#resolveObsidianBinary);
		return this.#spawnObsidian(bin, args, context?.signal, DEFAULT_OBSIDIAN_TIMEOUT_MS);
	}

	async #loadVaultDirectory(context?: ResolveContext | WriteContext): Promise<Map<string, string>> {
		if (cachedVaultDirectory) return cachedVaultDirectory;
		const result = await this.#spawn(["vaults", "verbose"], context);
		assertCliSuccess("vaults", result);
		cachedVaultDirectory = parseVaultDirectory(result.stdout);
		return cachedVaultDirectory;
	}

	async #resolveVaultRoot(ref: VaultReference, context?: ResolveContext | WriteContext): Promise<string> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass a plain file path in the vault:// URL (e.g. vault://<vault>/path/to/note.md), not a directory or CLI resource.
  2. Check the parsed kind with parseVaultUrl() before calling write, and route directory targets to other operations (e.g. resolve for listing).
  3. Create/choose a concrete file name if you intended to write into a directory.

Example fix

// before
await handler.write(parseInternalUrl("vault://_/notes/"), text); // directory -> throws
// after
await handler.write(parseInternalUrl("vault://_/notes/todo.md"), text);
Defensive patterns

Strategy: validation

Validate before calling

const parsed = parseVaultUrl(url);
if (parsed.kind !== "fs-file") {
  throw new Error(`vault:// write needs a plain file path, got kind=${parsed.kind}`);
}

Type guard

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

Try / catch

try {
  await handler.write(url, content);
} catch (err) {
  if (err instanceof Error && err.message === "vault:// write only supports plain file paths") {
    // fix the URL to a plain file path and retry once
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling handler.write(url, content) with a vault:// URL that parses to a non-fs-file kind — e.g. a directory URL like vault://_/notes/ or a URL targeting a CLI-backed resource — instead of a plain file path like vault://_/notes/idea.md.

Common situations: Agents or scripts generating vault:// URLs programmatically and passing directory URLs to write; mistaking the directory-listing URL form for a writable file target.

Related errors


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