{"record":{"id":"87ed8cb17076a3b9","repo":"can1357/oh-my-pi","slug":"no-built-in-uploader-or-serving-adapter-is-impleme-87ed8c","errorCode":null,"errorMessage":"no built-in uploader or serving adapter is implemented","messagePattern":"no built-in uploader or serving adapter is implemented","errorType":"exception","errorClass":"DestinationUnavailableError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders.ts","lineNumber":161,"sourceCode":"\t\t}\n\t\treturn createCommandUploader(command);\n\t}\n\n\tconst uploader =\n\t\tcreateAnonymousUploader(destination, config) ??\n\t\tcreateImageHostUploader(destination, config) ??\n\t\tcreateCloudDriveUploader(destination, config) ??\n\t\tcreateObjectStorageUploader(destination, config) ??\n\t\tcreateSelfHostedUploader(destination, config) ??\n\t\tcreateLegacyUploader(destination, config) ??\n\t\tcreateDiscordUploader(destination, config);\n\tif (uploader) return uploader;\n\n\tif (destination === \"provider-files\") {\n\t\tthrow new DestinationUnavailableError(destination, \"provider-native files must use the provider file channel\");\n\t}\n\tif (metadata.family === \"local-serving\" || metadata.family === \"tunnel\") return null;\n\tthrow new DestinationUnavailableError(destination, \"no built-in uploader or serving adapter is implemented\");\n}\n\n/** Wrap an uploader with per-hash memoization so bytes upload at most once. */\nexport function memoizeUploader(\n\tuploader: BlobUploader,\n): (hash: string, request: BlobUploadRequest) => Promise<BlobPublication | null> {\n\tconst byHash = new Map<string, Promise<BlobPublication | null>>();\n\treturn (hash, request) => {\n\t\tlet pending = byHash.get(hash);\n\t\tif (!pending) {\n\t\t\tpending = uploader.upload(request).catch(error => {\n\t\t\t\tbyHash.delete(hash);\n\t\t\t\tlogger.warn(\"blob-broker: upload failed; image stays inline\", {\n\t\t\t\t\tuploader: uploader.destination,\n\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t\treturn null;\n\t\t\t});","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders.ts#L143-L179","documentation":"The blob broker's createConfiguredUploader() resolves a destination name (e.g. 'r2', 's3') to an uploader implementation. Only certain destinations have built-in adapters; anything else that is not local-serving or tunnel (which return null, meaning handled elsewhere) has no implementation, so the broker throws DestinationUnavailableError. It is a deliberate 'not implemented yet' guard, not a configuration typo check.","triggerScenarios":"Calling createConfiguredUploader (via uploader() or configChecks()) with a destination string that has a registered metadata family but no built-in uploader adapter — i.e. any non-'provider-files', non-'local-serving', non-'tunnel' destination whose uploader factory was never implemented.","commonSituations":"A user configures blob uploads to a custom/exotic destination in settings expecting support; a plugin author adds a destination family without wiring an uploader; a new destination type was added to metadata before the adapter shipped.","solutions":["Check the destination name in config and switch to a supported destination (provider-files, local-serving, or tunnel)","Register a custom uploader before calling createConfiguredUploader so the memoized `uploader` short-circuit returns it instead of falling through to the throw","If you own the code, implement an uploader adapter for the destination family in packages/coding-agent/src/blob-broker/uploaders.ts","If you only need local serving, use a local-serving family destination, which returns null instead of throwing"],"exampleFix":"// before\nconst up = await createConfiguredUploader({ destination: \"gcs\", metadata });\n// after\nconst up = await createConfiguredUploader({ destination: \"local-serving\", metadata }); // or pass a pre-built uploader","handlingStrategy":"validation","validationCode":"const SUPPORTED = new Set([\"provider-files\", \"local-serving\", \"tunnel\"]);\nif (!SUPPORTED.has(destination) && !customUploader) {\n  throw new Error(`destination \"${destination}\" has no built-in uploader; provide one or pick a supported destination`);\n}","typeGuard":"function hasBuiltInAdapter(destination: string): boolean {\n  return [\"provider-files\", \"local-serving\", \"tunnel\"].includes(destination);\n}","tryCatchPattern":"try {\n  uploader = await createConfiguredUploader({ destination, metadata });\n} catch (err) {\n  if (err instanceof DestinationUnavailableError) {\n    logger.warn(\"destination unsupported, skipping upload\", { destination });\n    return null;\n  }\n  throw err;\n}","preventionTips":["Only configure destinations listed in the broker's supported set","Register a custom uploader for exotic destinations before resolving","Handle the null return for local-serving/tunnel families instead of expecting an uploader","Add a config-check (configChecks) before first real upload"],"tags":["blob-upload","not-implemented","configuration"],"backgroundTag":"unsupported-destination","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}