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 andView on GitHub (pinned to 9690622007)
Solutions
- Verify publicBaseUrl actually routes to the tunnel/forwarded port (curl the health path manually: curl -i https://<origin>/.well-known/omp-blob-health).
- For the "ssh" kind, ensure a reverse proxy on the remote host serves the sshRemotePort back to the forwarded port.
- Increase the probe tolerance (attempts/timeoutMs) if the tunnel is slow to register, then retry.
- Check the tunnel child's log (omp-blob-tunnel-*.log in tmpdir) for registration errors, and confirm network/firewall allows the tunnel provider.
- 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
- Confirm the remote side of ssh forwards actually proxies the forwarded port to the internet-facing origin.
- Point publicBaseUrl at the exact origin the tunnel publishes — no stale domains.
- Allow tunnels a moment to register at the edge; the built-in retries cover transient slowness only.
- Remember the design contract: probe failure degrades to inline base64, so build callers that tolerate null publications.
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
- ${argv[0]} exited with code ${exitCode} before reporting a t
- ${argv[0]} did not report a tunnel URL within ${READY_TIMEOU
- Cannot reach vmond at ${url}: ${detail}. Ensure vmon serve i
- timed out: {command}
- V2 remote compaction failed (${response.status} ${response.s
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d35af54defabcaa3.
Report an issue: GitHub.