can1357/oh-my-pi · error

Exposure health probe for ${destination} failed with status

Error message

Exposure health probe for ${destination} failed with status ${finalStatus}

What it means

probeExposureHealth verifies the public exposure (tunnel/forward URL) actually reaches the local blob server's /.well-known/omp-blob-health endpoint, requiring an exact 204. After exhausting its retry attempts (default 5 with 250ms backoff, 3s per-attempt timeout), it throws this error with the sanitized destination origin and the final status (HTTP <code>, timeout, or request failed). Callers degrade to inline base64 images.

Source

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

		try {
			const response = await fetchFn(healthUrl, {
				cache: "no-store",
				signal: AbortSignal.timeout(timeoutMs),
			});
			if (response.status === 204) return;
			finalStatus = `HTTP ${response.status}`;
			try {
				await response.body?.cancel();
			} catch {
				// The response status is authoritative even if body disposal fails.
			}
		} catch (error) {
			finalStatus = error instanceof DOMException && error.name === "TimeoutError" ? "timeout" : "request failed";
		}
		if (attempt + 1 < attempts && backoffMs > 0) await Bun.sleep(backoffMs);
	}

	throw new Error(`Exposure health probe for ${destination} failed with status ${finalStatus}`);
}

/**
 * SIGTERM, escalating to SIGKILL after a grace period. `tailscale funnel`
 * observably survives a bare SIGTERM mid-startup, and a leaked funnel child
 * blocks every later funnel invocation on the machine.
 */
function killTunnelProcess(proc: Bun.Subprocess): void {
	proc.kill();
	const timer = setTimeout(() => {
		if (proc.exitCode === null) proc.kill("SIGKILL");
	}, 2_000);
	timer.unref();
}

/**
 * Spawn a tunnel process with its output redirected to a temp log file and
 * poll the file until `extract` yields the public URL. Kills the child and

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify publicBaseUrl actually routes to the tunnel/forwarded port (curl the health path manually: curl -i https://<origin>/.well-known/omp-blob-health).
  2. For the "ssh" kind, ensure a reverse proxy on the remote host serves the sshRemotePort back to the forwarded port.
  3. Increase the probe tolerance (attempts/timeoutMs) if the tunnel is slow to register, then retry.
  4. Check the tunnel child's log (omp-blob-tunnel-*.log in tmpdir) for registration errors, and confirm network/firewall allows the tunnel provider.
  5. Accept the fallback: the broker degrades to inline base64 images, which still works.

Example fix

// before (config)
{ kind: "ssh", publicBaseUrl: "https://example.com", sshTarget: "user@host" } // remote serves nothing on 8787
// after: run a proxy on the remote host, e.g.
// ssh -N -R 8787:127.0.0.1:<port> user@host  +  remote nginx: listen 8787 -> proxy_pass http://127.0.0.1:8787
{ kind: "ssh", publicBaseUrl: "https://example.com", sshTarget: "user@host", sshRemotePort: 8787 }
Defensive patterns

Strategy: retry

Validate before calling

// verify the public origin yourself before relying on it:
// curl -fsSI https://<publicBaseUrl>/.well-known/omp-blob-health  (expect HTTP 204)
const res = await fetch(`${publicBaseUrl}/.well-known/omp-blob-health`, { signal: AbortSignal.timeout(3000) }).catch(() => null);
if (!res || res.status !== 204) console.warn("public origin does not reach the blob server yet");

Try / catch

try {
  await probeExposureHealth(baseUrl, fetch, { attempts: 5, backoffMs: 250 });
} catch (err) {
  logger.warn("exposure unreachable; falling back to inline base64", {
    baseUrl,
    detail: err instanceof Error ? err.message : String(err),
  });
}

Prevention

When it happens

Trigger: startExposure succeeded (tunnel process printed a URL) but the health probe never got a 204: tunnel not yet registered at the edge, remote server for "ssh" kind not proxying the forwarded port, publicBaseUrl pointing at the wrong origin, firewall blocking the edge, or the tunnel dying between URL print and probe.

Common situations: Tunnel URL advertised before edge registration completes (transient — retries usually fix it); ssh reverse forward set up but no web server configured on the remote host at sshRemotePort; publicBaseUrl misconfigured to a domain that doesn't route to the tunnel; corporate network blocking the tunnel provider.

Related errors


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