{"record":{"id":"f0481afb4556259d","repo":"can1357/oh-my-pi","slug":"unsupported-vault-file-op-rawop","errorCode":null,"errorMessage":"Unsupported vault:// file op: ${rawOp}","messagePattern":"Unsupported vault:// file op: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/vault-protocol.ts","lineNumber":197,"sourceCode":"function makeVaultReference(host: string): VaultReference {\n\tif (!host || host === \"_\") {\n\t\treturn { vault: null, active: true, forwardVault: false, display: \"_\" };\n\t}\n\treturn { vault: host, active: false, forwardVault: true, display: host };\n}\n\nfunction isFileOp(rawOp: string): rawOp is FileOp {\n\treturn FILE_OPS[rawOp as FileOp] === true;\n}\n\nfunction isVaultOp(rawOp: string): rawOp is VaultOp {\n\treturn VAULT_OPS[rawOp as VaultOp] === true;\n}\n\nfunction parseVaultOp(rawOp: string, hasFilePath: boolean): FileOp | VaultOp {\n\tif (hasFilePath) {\n\t\tif (!isFileOp(rawOp)) {\n\t\t\tthrow new Error(`Unsupported vault:// file op: ${rawOp}`);\n\t\t}\n\t\treturn rawOp;\n\t}\n\tif (!isVaultOp(rawOp)) {\n\t\tthrow new Error(`Unsupported vault:// vault op: ${rawOp}`);\n\t}\n\treturn rawOp;\n}\n\nexport function parseVaultUrl(input: string | InternalUrl): ParsedVaultUrl {\n\tconst url = typeof input === \"string\" ? parseInternalUrl(input) : input;\n\tconst host = url.rawHost || url.hostname;\n\tconst params = paramsFromUrl(url);\n\tconst rawOp = typeof params.op === \"string\" ? params.op : undefined;\n\tconst { rawPathname, relativePath, hasPath, isDirectory } = decodeVaultPath(url);\n\n\tif (!host && !hasPath && !rawOp) {\n\t\treturn { kind: \"list-vaults\", url: url.href, params };","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/vault-protocol.ts#L179-L215","documentation":"When a vault:// URL includes a file path plus an `op` query parameter, `parseVaultOp` only accepts the whitelisted file operations (outline, backlinks, links, tags, properties, tasks, wordcount, history, base). Any other `op` value with a path present is rejected with this error. The whitelist is intentional: file ops map one-to-one to Obsidian CLI subcommands.","triggerScenarios":"`vault://_/notes/foo.md?op=summarize`, `?op=search` (search is a vault op, not a file op), `?op=Outline` (case-sensitive), or a typo like `?op=backlink` while the URL has a non-empty path.","commonSituations":"Assuming every vault op also works per-file (e.g. `search` with a path); typos or wrong casing in generated URLs; using an op added in a newer Obsidian CLI version that this handler does not yet whitelist.","solutions":["Use one of the supported file ops: outline, backlinks, links, tags, properties, tasks, wordcount, history, base.","If you intended a vault-wide op (search, daily, orphans, unresolved, deadends, bases, bookmarks, recents, templates, aliases, property), remove the file path from the URL.","Fix casing/typos — op matching is exact and case-sensitive.","If the op genuinely exists in the Obsidian CLI, add it to FILE_OPS in vault-protocol.ts."],"exampleFix":"// before\nconst url = \"vault://_/notes/idea.md?op=search&q=test\"; // search is not a file op\n// after\nconst url = \"vault://_/?op=search&q=test\"; // vault-wide search, no path\n// or per-file:\nconst url2 = \"vault://_/notes/idea.md?op=backlinks\";","handlingStrategy":"validation","validationCode":"const FILE_OPS = [\"outline\",\"backlinks\",\"links\",\"tags\",\"properties\",\"tasks\",\"wordcount\",\"history\",\"base\"];\nfunction isValidFileOp(op: string): boolean { return FILE_OPS.includes(op); }\n// check isValidFileOp(op) before appending ?op= to a path-bearing vault:// URL","typeGuard":"function isFileOpOp(op: string): op is \"outline\"|\"backlinks\"|\"links\"|\"tags\"|\"properties\"|\"tasks\"|\"wordcount\"|\"history\"|\"base\" {\n  return [\"outline\",\"backlinks\",\"links\",\"tags\",\"properties\",\"tasks\",\"wordcount\",\"history\",\"base\"].includes(op);\n}","tryCatchPattern":"try {\n  const res = await handler.resolve(parseInternalUrl(url));\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Unsupported vault:// file op:\")) {\n    // fall back to reading the file itself or a supported op\n  } else throw err;\n}","preventionTips":["Keep a shared constant list of valid file ops and build URLs from it","Remember ops are case-sensitive: use lowercase exactly as documented","Vault-wide ops (search, daily, ...) are not file ops — drop the path or switch ops","Check the Obsidian CLI version before using newly added ops"],"tags":["url","validation","whitelist","api-misuse"],"backgroundTag":"unsupported-operation","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}