{"record":{"id":"8a5393542e2a9a31","repo":"can1357/oh-my-pi","slug":"credentials-usernamekey-and-passwordkey-must","errorCode":null,"errorMessage":"credentials ${usernameKey} and ${passwordKey} must be configured together","messagePattern":"credentials (.+?) and (.+?) must be configured together","errorType":"validation","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":159,"sourceCode":"\tif (!raw) throw new LegacyDestinationError(destination, \"the upload response did not include a direct image URL\");\n\treturn httpUrl(destination, raw, base);\n}\n\nfunction basicAuthorization(username: string, password: string): string {\n\treturn `Basic ${Buffer.from(`${username}:${password}`, \"utf8\").toString(\"base64\")}`;\n}\n\nfunction optionalBasicHeaders(\n\tdestination: BlobDestinationId,\n\tconfig: DestinationRuntimeConfig,\n\tusernameKey: string,\n\tpasswordKey: string,\n): Headers | undefined {\n\tconst username = credentialString(config, usernameKey);\n\tconst password = credentialString(config, passwordKey);\n\tif (!username && !password) return undefined;\n\tif (!username || !password) {\n\t\tthrow new LegacyDestinationError(\n\t\t\tdestination,\n\t\t\t`credentials ${usernameKey} and ${passwordKey} must be configured together`,\n\t\t);\n\t}\n\treturn new Headers({ Authorization: basicAuthorization(username, password) });\n}\n\nfunction xmlEntityDecode(value: string): string {\n\treturn value\n\t\t.replaceAll(\"&quot;\", '\"')\n\t\t.replaceAll(\"&apos;\", \"'\")\n\t\t.replaceAll(\"&lt;\", \"<\")\n\t\t.replaceAll(\"&gt;\", \">\")\n\t\t.replaceAll(\"&amp;\", \"&\");\n}\n\nfunction xmlAttribute(source: string, element: string, attribute: string): string | undefined {\n\tconst elementMatch = source.match(new RegExp(`<${element}\\\\b[^>]*>`, \"i\"));","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L141-L177","documentation":"This LegacyDestinationError is thrown by optionalBasicHeaders() when exactly one of a destination's paired basic-auth credentials is configured (username without password, or vice versa). Since the two are combined into a single HTTP Basic Authorization header, a half-configured pair can never authenticate, so the library fails fast with a message naming both config keys.","triggerScenarios":"Setting only the username key (e.g. for transfer-sh: only `username`/token without `password`, or lobfile/localhostr pairs) in DestinationRuntimeConfig; the message template embeds the actual key names, e.g. 'credentials basicUsername and basicPassword must be configured together'; thrown while building request headers in createLegacyUploader -> headers.","commonSituations":"Users fill in a username during setup and skip the password assuming it is optional; secrets managers injecting only one of the two variables; rotating credentials and removing one value; copy-pasting a config example with only one field.","solutions":["Set both credential keys in the destination config (the two key names are given verbatim in the error message)","If the endpoint needs no auth, clear BOTH keys so optionalBasicHeaders() returns undefined instead of throwing","Check environment/secret injection so both values resolve — one may be empty string or missing","Re-run with both values present and verify the Authorization header is sent"],"exampleFix":"// before\n{ basicUsername: \"alice\" }\n// after\n{ basicUsername: \"alice\", basicPassword: \"secret\" }","handlingStrategy":"validation","validationCode":"const u = config.basicUsername, p = config.basicPassword;\nif ((u && !p) || (!u && p)) {\n  throw new Error(\"basicUsername and basicPassword must be configured together\");\n}","typeGuard":"function hasCompleteBasicAuth(c: { basicUsername?: string; basicPassword?: string }): boolean {\n  return (!!c.basicUsername && !!c.basicPassword) || (!c.basicUsername && !c.basicPassword);\n}","tryCatchPattern":"try {\n  await uploader.upload(request);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"must be configured together\")) {\n    // set or clear both credential keys named in the message\n  } else throw err;\n}","preventionTips":["Always set credential pairs together; never leave one blank","Validate config at load time: both keys present or both absent","Check secret-manager/env injection so neither half of the pair is dropped","When rotating credentials, update both values atomically"],"tags":["configuration","authentication","credentials","upload"],"backgroundTag":"incomplete-credentials-config","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}