{"record":{"id":"5ea42211d8537d08","repo":"can1357/oh-my-pi","slug":"vault-op-requires-name-query-parameter","errorCode":null,"errorMessage":"vault://${op} requires '${name}' query parameter","messagePattern":"vault://(.+?) requires '(.+?)' query parameter","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/vault-protocol.ts","lineNumber":504,"sourceCode":"\treturn { files, folders };\n}\n\nfunction formatVaultPathForLink(ref: VaultReference, relativePath: string, trailingSlash: boolean): string {\n\tconst encodedVault = ref.active ? \"_\" : encodePathComponent(ref.display);\n\tconst encodedPath = encodeRelativePath(relativePath);\n\tconst suffix = trailingSlash ? \"/\" : \"\";\n\treturn encodedPath ? `vault://${encodedVault}/${encodedPath}${suffix}` : `vault://${encodedVault}/`;\n}\n\nfunction paramString(params: VaultParams, name: string): string | undefined {\n\tconst value = params[name];\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction requireParam(params: VaultParams, name: string, op: string): string {\n\tconst value = paramString(params, name);\n\tif (value) return value;\n\tthrow new Error(`vault://${op} requires '${name}' query parameter`);\n}\n\nfunction validateQueryPath(params: VaultParams, name: string): string | undefined {\n\tconst value = paramString(params, name);\n\tif (!value) return undefined;\n\ttry {\n\t\tvalidateRelativePath(value.replaceAll(\"\\\\\", \"/\"));\n\t} catch (error) {\n\t\tthrow toVaultValidationError(error);\n\t}\n\treturn value;\n}\n\nexport function buildObsidianCliInvocation(\n\tparsed: Extract<ParsedVaultUrl, { kind: \"file-op\" | \"vault-op\" }>,\n): CliInvocation {\n\tif (parsed.kind === \"file-op\") {\n\t\tconst pathArg = `path=${parsed.relativePath}`;","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/vault-protocol.ts#L486-L522","documentation":"Some ops need mandatory query parameters enforced by `requireParam`: `base` requires `view`, `search` requires `q`, `tag` requires `tag` (or `name`), and `property` requires both `name` and `path`. When the parameter is absent or an empty string, this error names the op and the missing parameter. Empty-string params are stored as `true` by paramsFromUrl and are treated as missing by paramString, so `?q=` also triggers it.","triggerScenarios":"`vault://_/?op=search` (no q), `vault://_/b.base?op=base` (no view), `vault://_/?op=tag` (no tag/name), `vault://_/?op=property&name=status` (no path), or `?q=` with an empty value.","commonSituations":"Programmatically building URLs where the user's query/tag string was empty; stripping query params during URL rewriting; copy-pasting example URLs and omitting the placeholder parameter.","solutions":["Add the named parameter: `?q=<query>` for search, `?view=<view>` for base, `?tag=<name>` (or `?name=`) for tag, and `?name=<property>&path=<file>` for property.","Ensure the value is non-empty — `?q=` counts as missing.","Percent-encode parameter values (encodeURIComponent) so `&`, `=`, `#` in the value don't split the query string.","Validate inputs before building the URL so empty search/tag values are rejected upstream."],"exampleFix":"// before\nconst url = `vault://_/?op=search&q=${query}`; // query may be \"\"\n// after\nif (!query) throw new Error(\"search requires a non-empty query\");\nconst url = `vault://_/?op=search&q=${encodeURIComponent(query)}`;","handlingStrategy":"validation","validationCode":"const REQUIRED: Record<string, string[]> = { base: [\"view\"], search: [\"q\"], tag: [\"tag\", \"name\"], property: [\"name\", \"path\"] };\nfunction hasRequiredParams(op: string, params: URLSearchParams): boolean {\n  const req = REQUIRED[op];\n  if (!req) return true;\n  return req.some(name => params.get(name)?.length) // tag/property need all; simplify per-op as needed\n    || (op === \"tag\" || op === \"property\" ? req.every(n => params.get(n)?.length) : false);\n}","typeGuard":"function hasParam(params: URLSearchParams, name: string): boolean {\n  const v = params.get(name);\n  return v !== null && v.length > 0;\n}","tryCatchPattern":"try {\n  const res = await handler.resolve(parseInternalUrl(url));\n} catch (err) {\n  const m = err instanceof Error && err.message.match(/^vault:\\/\\/(\\S+) requires '(\\S+)' query parameter$/);\n  if (m) {\n    url = withParam(url, m[2], promptFor(m[2])); // supply the named param and retry\n  } else throw err;\n}","preventionTips":["Encode values with encodeURIComponent so `&`/`=` in queries don't split parameters","Reject empty strings upstream — `?q=` counts as missing","Keep a per-op required-parameter table next to your URL builder","Remember `tag` accepts `name` OR `tag`; `property` needs both `name` and `path`"],"tags":["url","query-parameters","validation","api-misuse"],"backgroundTag":"missing-required-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}