{"record":{"id":"7a4a693a98a16f95","repo":"can1357/oh-my-pi","slug":"destination-option-key-must-be-a-non-empty-stri","errorCode":null,"errorMessage":"Destination option ${key} must be a non-empty string","messagePattern":"Destination option (.+?) must be a non-empty string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":87,"sourceCode":"}\n\nfunction plikUpload(value: unknown): PlikUpload {\n\tif (typeof value !== \"object\" || value === null || !(\"id\" in value) || !(\"uploadToken\" in value)) {\n\t\tthrow new Error(\"plik upload metadata did not include an id and upload token\");\n\t}\n\tconst id = identifier(value.id);\n\tconst uploadToken = nonEmptyString(value.uploadToken);\n\tif (!id || !uploadToken) throw new Error(\"plik upload metadata did not include an id and upload token\");\n\tlet downloadBase: string | undefined;\n\tif (\"downloadURL\" in value) downloadBase = nonEmptyString(value.downloadURL);\n\tif (!downloadBase && \"downloadDomain\" in value) downloadBase = nonEmptyString(value.downloadDomain);\n\treturn { id, uploadToken, ...(downloadBase ? { downloadBase } : {}) };\n}\n\nfunction requiredStringOption(config: DestinationRuntimeConfig, key: string): string {\n\tconst value = requireOption(config, key);\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new Error(`Destination option ${key} must be a non-empty string`);\n\t}\n\treturn value.trim();\n}\n\nfunction pathParts(value: string | undefined): string[] {\n\tif (!value) return [];\n\tconst parts = value.replaceAll(\"\\\\\", \"/\").split(\"/\");\n\tconst result: string[] = [];\n\tfor (const part of parts) {\n\t\tif (!part || part === \".\") continue;\n\t\tif (part === \"..\" || part.includes(\"\\0\"))\n\t\t\tthrow new Error(\"Destination paths cannot contain parent traversal or NUL bytes\");\n\t\tresult.push(part);\n\t}\n\treturn result;\n}\n\nfunction safeFileName(request: BlobUploadRequest): string {","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L69-L105","documentation":"Thrown by requiredStringOption when a destination config option (fetched via requireOption) is present but is not a string, or is a string that is empty/whitespace-only. Self-hosted uploaders call it for every mandatory option (host, publicBase, root, apiUrl, repositoryId, base), so a bad option value fails fast at uploader creation.","triggerScenarios":"Creating any self-hosted blob destination (webdav, ftp, sftp, gitea, etc.) where a required option is set to an empty string, a number, boolean, null, or whitespace — e.g. `\"host\": \"\"` or `\"apiUrl\": 8080` in the destination config.","commonSituations":"Empty env-var interpolation in config (HOST= empty), YAML/JSON type coercion turning a value into a number, copy-pasting a config template without filling fields, or trimming leaving only whitespace.","solutions":["Open the destination config and set the named option (shown in the error message) to a real non-empty string","Check where the value comes from — an unset env variable interpolating to \"\" is the usual culprit","Quote string values in YAML/JSON so numbers/booleans don't leak in (e.g. port: \"22\" not port: 22)","Validate the full destination config before saving it (run each required option through typeof v === 'string' && v.trim() !== '')"],"exampleFix":"// before\n{ \"type\": \"webdav\", \"host\": \"\", \"root\": 42 }\n// after\n{ \"type\": \"webdav\", \"host\": \"cloud.example.com\", \"root\": \"/remote.php/webdav\" }","handlingStrategy":"validation","validationCode":"function requireNonEmptyString(config: Record<string, unknown>, key: string): string {\n  const v = config[key];\n  if (typeof v !== \"string\" || v.trim() === \"\") throw new Error(`Destination option ${key} must be a non-empty string (got ${JSON.stringify(v)})`);\n  return v.trim();\n}\n// validate all required keys before creating the destination\nfor (const k of [\"host\", \"root\", \"publicBase\"]) requireNonEmptyString(destConfig, k);","typeGuard":"function isFilledString(value: unknown): value is string {\n  return typeof value === \"string\" && value.trim().length > 0;\n}","tryCatchPattern":"try {\n  const uploader = createWebdavUploader(dest);\n} catch (err) {\n  const m = (err as Error).message.match(/Destination option (\\S+) must be a non-empty string/);\n  if (m) throw new Error(`Fix your destination config: option '${m[1]}' is missing or empty`);\n  throw err;\n}","preventionTips":["Fill every required option in the destination template before saving","Check env vars used in config interpolation are actually set (empty HOST= yields \"\")","Quote values so YAML/JSON don't coerce them to numbers/booleans","Validate the config with a schema (zod/arktype) at load time"],"tags":["configuration","validation","self-hosted","destinations"],"backgroundTag":"invalid-config-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}