can1357/oh-my-pi · error · DestinationUnavailableError
the destination does not expose the local blob server
Error message
the destination does not expose the local blob server
What it means
During #start, after the local blob HTTP server binds, the backend re-validates that the configured destination kind actually exposes the local server (isServeKind). If not, it throws DestinationUnavailableError. In practice this is a defensive consistency check: non-serve kinds take the uploader path and never reach #start, so hitting it means the kind/config changed underneath the backend.
Source
Thrown at packages/coding-agent/src/blob-broker/broker.ts:142
async #start(): Promise<string | null> {
try {
this.#server = Bun.serve({
hostname: this.#config.bindHost,
port: 0,
fetch: request => {
if (new URL(request.url).pathname === "/.well-known/omp-blob-health") {
return new Response(null, { status: 204 });
}
return this.#store.serve(request);
},
});
this.#server.unref();
const port = this.#server.port;
if (port === undefined) throw new Error("blob server bound without a TCP port");
const kind = this.#config.kind;
if (!isServeKind(kind)) {
throw new DestinationUnavailableError(kind, "the destination does not expose the local blob server");
}
const exposureConfig: ExposureConfig = {
kind,
publicBaseUrl: this.#config.publicBaseUrl,
bindHost: this.#config.bindHost,
sshTarget: this.#config.sshTarget,
sshRemotePort: this.#config.sshRemotePort,
options: this.#config.options,
credentials: this.#config.credentials,
};
const exposure = await startExposure(exposureConfig, port);
try {
await probeExposureHealth(exposure.baseUrl);
} catch (error) {
exposure.stop();
throw error;
}
this.#exposure = exposure;View on GitHub (pinned to 9690622007)
Solutions
- Use a valid serve kind (direct, ssh, or one of the tunnel kinds) for the backend config.
- Ensure the config passed to LocalBlobBackend is not mutated after construction; rebuild the backend with the new config instead.
- Route upload-only destinations through their uploader path so #start is never invoked.
Example fix
// before
const backend = new LocalBlobBackend(config); // kind: custom upload id, uploader missing
await backend.ensureStarted();
// after
const backend = new LocalBlobBackend({ ...config, kind: "direct" });
await backend.ensureStarted(); Defensive patterns
Strategy: try-catch
Validate before calling
import { isServeKind } from "./blob-broker/broker";
if (!isServeKind(config.kind)) {
throw new Error(`kind ${config.kind} is not a serve destination; it must go through the uploader path`);
} Try / catch
try {
await backend.ensureStarted();
} catch (err) {
if (err instanceof DestinationUnavailableError) {
logger.warn("backend kind inconsistent with serve path", { kind: config.kind });
} else throw err;
} Prevention
- Treat LocalBlobBackend config as immutable after construction — rebuild instead of mutating.
- Only call ensureStarted on serve-kind backends; upload kinds skip it by design.
- Let the constructor's uploader/serve resolution be the single source of truth for routing.
When it happens
Trigger: ensureStarted() on a LocalBlobBackend whose config.kind is not a serve kind but which was constructed without an uploader — an internally inconsistent config, or kind mutated after construction.
Common situations: Custom/test code constructing a backend with an uploader-like kind but bypassing the constructor's uploader resolution; config object mutated between construction and start; exotic custom BlobDestinationId values.
Related errors
- modified change requires peer root
- No model configured
- Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL
- Cannot register custom API "${api}": built-in API names are
- Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePa
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8f32a4819248c7bc.
Report an issue: GitHub.