{"record":{"id":"d50282d5c834dcc2","repo":"can1357/oh-my-pi","slug":"vault-path-resolution-only-supports-plain-files","errorCode":null,"errorMessage":"vault:// path resolution only supports plain filesystem paths","messagePattern":"vault:// path resolution only supports plain filesystem paths","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/vault-protocol.ts","lineNumber":427,"sourceCode":"\tlet current = targetPath;\n\twhile (true) {\n\t\tensureWithinRoot(current, rootPath);\n\t\ttry {\n\t\t\treturn fs.realpathSync(current);\n\t\t} catch (error) {\n\t\t\tif (!isEnoent(error)) throw error;\n\t\t\tconst parent = path.dirname(current);\n\t\t\tif (parent === current) throw error;\n\t\t\tcurrent = parent;\n\t\t}\n\t}\n}\n\nexport function resolveVaultUrlToPath(input: string | InternalUrl): string {\n\tif (!isVaultEnabled()) throw new VaultDisabledError();\n\tconst parsed = parseVaultUrl(input);\n\tif (parsed.kind !== \"fs-file\" && parsed.kind !== \"fs-dir\") {\n\t\tthrow new Error(\"vault:// path resolution only supports plain filesystem paths\");\n\t}\n\n\tconst cachedRoot = getCachedVaultRoot(parsed.ref);\n\tif (!cachedRoot) {\n\t\tthrow new Error(\n\t\t\t\"vault:// path resolution requires a cached vault root; read vault:// first or use the write tool\",\n\t\t);\n\t}\n\n\tconst resolvedRoot = fs.realpathSync(cachedRoot);\n\tconst targetPath = parsed.relativePath ? path.resolve(resolvedRoot, parsed.relativePath) : resolvedRoot;\n\tensureWithinRoot(targetPath, resolvedRoot);\n\n\ttry {\n\t\tconst realTarget = fs.realpathSync(targetPath);\n\t\tensureWithinRoot(realTarget, resolvedRoot);\n\t} catch (error) {\n\t\tif (!isEnoent(error)) throw error;","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/vault-protocol.ts#L409-L445","documentation":"`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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Strip the `op` query parameter and resolve the underlying path portion of the URL.","For op/vault-info/list URLs, use VaultProtocolHandler.resolve() to get an InternalResource instead of a filesystem path.","Guard the call: parse the URL first with parseVaultUrl and only call resolveVaultUrlToPath when kind is fs-file or fs-dir."],"exampleFix":"// before\nconst p = resolveVaultUrlToPath(\"vault://_/notes/idea.md?op=backlinks\"); // throws\n// after\nconst parsed = parseVaultUrl(\"vault://_/notes/idea.md?op=backlinks\");\nconst p = parsed.kind === \"fs-file\" || parsed.kind === \"fs-dir\"\n  ? resolveVaultUrlToPath(parsed)\n  : null; // handle ops via handler.resolve() instead","handlingStrategy":"type-guard","validationCode":"import { parseVaultUrl } from \"./internal-urls/vault-protocol\";\nconst parsed = parseVaultUrl(url);\nif (parsed.kind !== \"fs-file\" && parsed.kind !== \"fs-dir\") {\n  throw new Error(`cannot resolve ${parsed.kind} to a filesystem path`);\n}\nconst p = resolveVaultUrlToPath(parsed);","typeGuard":"function isPlainFsVaultUrl(parsed: ReturnType<typeof parseVaultUrl>): parsed is Extract<ReturnType<typeof parseVaultUrl>, { kind: \"fs-file\" | \"fs-dir\" }> {\n  return parsed.kind === \"fs-file\" || parsed.kind === \"fs-dir\";\n}","tryCatchPattern":"try {\n  const p = resolveVaultUrlToPath(url);\n} catch (err) {\n  if (err instanceof Error && err.message === \"vault:// path resolution only supports plain filesystem paths\") {\n    // route op/info/list URLs through handler.resolve() instead\n  } else throw err;\n}","preventionTips":["parseVaultUrl first and branch on parsed.kind before choosing a resolver","Strip ?op= parameters if you only need the underlying note/folder path","Use handler.resolve() for op-based, vault-info, and list-vaults URLs","Type helper signatures to accept only fs-file/fs-dir parse results"],"tags":["url","api-misuse","validation","type-mismatch"],"backgroundTag":"unsupported-url-kind","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}