{"record":{"id":"65db33e57341bc93","repo":"can1357/oh-my-pi","slug":"destination-option-key-must-be-a-string-65db33","errorCode":null,"errorMessage":"Destination option ${key} must be a string","messagePattern":"Destination option (.+?) must be a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-object-storage.ts","lineNumber":71,"sourceCode":"\tauthorizationToken: string;\n\tapiUrl: string;\n\tdownloadUrl: string;\n}\n\ninterface B2Bucket {\n\tbucketId: string;\n\tbucketName: string;\n\tbucketType: string;\n}\n\ninterface B2UploadTarget {\n\tuploadUrl: string;\n\tauthorizationToken: string;\n}\n\nfunction requiredString(config: DestinationRuntimeConfig, key: string): string {\n\tconst value = requireOption(config, key);\n\tif (typeof value !== \"string\") throw new Error(`Destination option ${key} must be a string`);\n\treturn value;\n}\n\nfunction configuredPrefix(config: DestinationRuntimeConfig): string | undefined {\n\treturn optionString(config, \"keyPrefix\") ?? optionString(config, \"prefix\") ?? optionString(config, \"path\");\n}\n\nfunction objectKey(prefix: string | undefined, filename: string): string {\n\tconst cleanPrefix = prefix?.replaceAll(\"\\\\\", \"/\").replace(/^\\/+|\\/+$/g, \"\");\n\treturn cleanPrefix ? `${cleanPrefix}/${filename}` : filename;\n}\n\nfunction encodePath(path: string): string {\n\treturn path\n\t\t.split(\"/\")\n\t\t.map(segment =>\n\t\t\tencodeURIComponent(segment).replace(\n\t\t\t\t/[!'()*]/g,","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-object-storage.ts#L53-L89","documentation":"requiredString validates that a destination runtime option exists and is a string, throwing a plain Error when the value is present but of the wrong type (e.g. number or boolean). It guards options like accountId, bucket, container, and authorizationToken before they reach the S3/B2/object-storage uploaders.","triggerScenarios":"A destination config supplies an option that requiredString reads (bucket, accountId, container, uploadUrl, authorizationToken, etc.) with a non-string JSON value — e.g. \"bucket\": 12345 or \"uploadUrl\": true — or a number-like ID entered without quotes.","commonSituations":"Config file edited by hand where a numeric account/bucket ID was written as a JSON number; programmatic config builder passing numbers; YAML/JSON type coercion turning an all-digit ID into a number.","solutions":["Quote the option value in the destination config so it is a JSON string: \"bucket\": \"12345\".","Inspect the config with a JSON validator to find which option under Destination option <key> is non-string.","If the option is genuinely numeric upstream, stringify it in the config layer before handing it to the broker.","Check for config-generation code that passes raw numbers instead of String(id)."],"exampleFix":"// before\n{ \"destination\": { \"accountId\": 123456, \"bucket\": 98765 } }\n// after\n{ \"destination\": { \"accountId\": \"123456\", \"bucket\": \"98765\" } }","handlingStrategy":"validation","validationCode":"const OPTION_KEYS = [\"accountId\", \"bucket\", \"container\", \"uploadUrl\", \"authorizationToken\"] as const;\nfor (const key of OPTION_KEYS) {\n  const v = config[key];\n  if (v !== undefined && typeof v !== \"string\") {\n    throw new Error(`config option ${key} must be a string, got ${typeof v}`);\n  }\n}","typeGuard":"function isStringOption(config: Record<string, unknown>, key: string): config is Record<string, string> {\n  return config[key] === undefined || typeof config[key] === \"string\";\n}","tryCatchPattern":"try {\n  const uploader = createObjectStorageUploader(destination, config);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Destination option\")) {\n    const key = err.message.match(/Destination option (\\w+)/)?.[1];\n    console.error(`Fix config: option '${key}' must be quoted as a string`);\n  } else throw err;\n}","preventionTips":["always quote IDs (account/bucket numbers) in destination configs","run JSON schema validation over the config file at startup","avoid hand-editing configs where editors auto-coerce digit-only values to numbers","use a typed config loader that rejects non-string option values before upload time"],"tags":["configuration","validation","typescript","object-storage"],"backgroundTag":"config-type-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}