{"record":{"id":"8f32a4819248c7bc","repo":"can1357/oh-my-pi","slug":"the-destination-does-not-expose-the-local-blob-ser","errorCode":null,"errorMessage":"the destination does not expose the local blob server","messagePattern":"the destination does not expose the local blob server","errorType":"exception","errorClass":"DestinationUnavailableError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/broker.ts","lineNumber":142,"sourceCode":"\n\tasync #start(): Promise<string | null> {\n\t\ttry {\n\t\t\tthis.#server = Bun.serve({\n\t\t\t\thostname: this.#config.bindHost,\n\t\t\t\tport: 0,\n\t\t\t\tfetch: request => {\n\t\t\t\t\tif (new URL(request.url).pathname === \"/.well-known/omp-blob-health\") {\n\t\t\t\t\t\treturn new Response(null, { status: 204 });\n\t\t\t\t\t}\n\t\t\t\t\treturn this.#store.serve(request);\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.#server.unref();\n\t\t\tconst port = this.#server.port;\n\t\t\tif (port === undefined) throw new Error(\"blob server bound without a TCP port\");\n\t\t\tconst kind = this.#config.kind;\n\t\t\tif (!isServeKind(kind)) {\n\t\t\t\tthrow new DestinationUnavailableError(kind, \"the destination does not expose the local blob server\");\n\t\t\t}\n\t\t\tconst exposureConfig: ExposureConfig = {\n\t\t\t\tkind,\n\t\t\t\tpublicBaseUrl: this.#config.publicBaseUrl,\n\t\t\t\tbindHost: this.#config.bindHost,\n\t\t\t\tsshTarget: this.#config.sshTarget,\n\t\t\t\tsshRemotePort: this.#config.sshRemotePort,\n\t\t\t\toptions: this.#config.options,\n\t\t\t\tcredentials: this.#config.credentials,\n\t\t\t};\n\t\t\tconst exposure = await startExposure(exposureConfig, port);\n\t\t\ttry {\n\t\t\t\tawait probeExposureHealth(exposure.baseUrl);\n\t\t\t} catch (error) {\n\t\t\t\texposure.stop();\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthis.#exposure = exposure;","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/broker.ts#L124-L160","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst backend = new LocalBlobBackend(config); // kind: custom upload id, uploader missing\nawait backend.ensureStarted();\n// after\nconst backend = new LocalBlobBackend({ ...config, kind: \"direct\" });\nawait backend.ensureStarted();","handlingStrategy":"try-catch","validationCode":"import { isServeKind } from \"./blob-broker/broker\";\nif (!isServeKind(config.kind)) {\n  throw new Error(`kind ${config.kind} is not a serve destination; it must go through the uploader path`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await backend.ensureStarted();\n} catch (err) {\n  if (err instanceof DestinationUnavailableError) {\n    logger.warn(\"backend kind inconsistent with serve path\", { kind: config.kind });\n  } else throw err;\n}","preventionTips":["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."],"tags":["configuration","invariant-violation"],"backgroundTag":"unsupported-destination-kind","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}