can1357/oh-my-pi · error · DestinationUnavailableError

no built-in uploader or serving adapter is implemented

Error message

no built-in uploader or serving adapter is implemented

What it means

The LocalBlobBackend constructor resolves the configured destination kind either to a serve adapter (tunnel/direct kinds) or an uploader factory. If the kind is neither in SERVE_KINDS nor has a registered uploader, no adapter exists for it and the constructor throws DestinationUnavailableError. This guards against misconfigured or unknown destination ids reaching the broker.

Source

Thrown at packages/coding-agent/src/blob-broker/broker.ts:104

	#exposure: { baseUrl: string; stop(): void } | undefined;
	#startPromise: Promise<string | null> | undefined;
	#upload: ((hash: string, request: BlobUploadRequest) => Promise<BlobPublication | null>) | undefined;
	#fetch: typeof globalThis.fetch;
	#dead = false;

	/** Create one local serving or configured upload backend. */
	constructor(config: BlobBrokerWorkerConfig, fetchFn: typeof globalThis.fetch = globalThis.fetch) {
		this.#config = config;
		this.#fetch = fetchFn;
		const servesBlobs = isServeKind(config.kind);
		const uploader = servesBlobs
			? null
			: createConfiguredUploader(config.kind, {
					options: config.options,
					credentials: config.credentials,
				});
		if (!servesBlobs && !uploader) {
			throw new DestinationUnavailableError(config.kind, "no built-in uploader or serving adapter is implemented");
		}
		this.#store = new BlobRegistry({ persist: config.persist });
		if (uploader) this.#upload = memoizeUploader(uploader);
	}

	/** Whether this backend can render blobs on fetch. */
	get supportsLazy(): boolean {
		return this.#upload === undefined;
	}

	/**
	 * Start the local server and exposure once (serve mode); resolves to the
	 * public base URL or `null` after a failure (sticky for this backend).
	 */
	ensureStarted(): Promise<string | null> {
		if (this.#upload) return Promise.resolve(null);
		this.#startPromise ??= this.#start();
		return this.#startPromise;

View on GitHub (pinned to 9690622007)

Solutions

  1. Correct the destination kind in the blob/imageUrls settings to a supported value (e.g. cloudflared, ngrok, tailscale, ssh, direct).
  2. Upgrade omp so the destination kind's adapter exists.
  3. Run the blob broker doctor command to validate the configured destination before starting sessions.

Example fix

// before
new LocalBlobBackend({ kind: "cloudflare" as BlobDestinationId, options: {}, credentials: {} });
// after
new LocalBlobBackend({ kind: "cloudflared", options: {}, credentials: {} });
Defensive patterns

Strategy: validation

Validate before calling

import { isServeKind, isUploaderKind } from "./blob-broker/broker";
const SERVE = ["cloudflared","ngrok","tailscale","ssh","direct","localhost-run","pinggy","devtunnel","zrok","bore","named-cloudflared"];
if (!isServeKind(config.kind) && !isUploaderKind(config.kind)) {
  throw new Error(`destination "${config.kind}" has no adapter; pick one of: ${SERVE.join(", ")}`);
}

Type guard

function isKnownDestination(kind: string): kind is BlobDestinationId {
  return ["cloudflared","ngrok","tailscale","ssh","direct","localhost-run","pinggy","devtunnel","zrok","bore","named-cloudflared"].includes(kind);
}

Try / catch

try {
  backend = new LocalBlobBackend(config);
} catch (err) {
  if (err instanceof DestinationUnavailableError) {
    logger.warn("destination unavailable; disabling image URLs", { kind: config.kind });
    backend = null;
  } else throw err;
}

Prevention

When it happens

Trigger: Constructing LocalBlobBackend with a BlobBrokerWorkerConfig whose kind is not one of the serve kinds (cloudflared, ngrok, tailscale, ssh, direct, localhost-run, pinggy, devtunnel, zrok, bore, named-cloudflared) and has no uploader implementation — e.g. a typo'd destination id or a new kind added to config before the adapter shipped.

Common situations: Settings file contains an unsupported imageUrls destination string; version skew where a config written by a newer omp is read by an older binary; typo like "cloudflare" instead of "cloudflared".

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e81e8f850ad4efe0. Report an issue: GitHub.