can1357/oh-my-pi · error
imageUrls exposure "${name}" requires the ${name} binary on
Error message
imageUrls exposure "${name}" requires the ${name} binary on PATH What it means
requireBinary resolves a tunnel/forward helper binary (cloudflared, ngrok, tailscale, ssh, devtunnel, zrok, bore) via $which and throws when it is not on PATH. Each exposure kind that spawns a child process needs its binary, and the broker refuses to start the exposure without it (the caller then falls back to inline base64 images).
Source
Thrown at packages/coding-agent/src/blob-broker/exposure.ts:153
}
/** Public frontend origin printed by `zrok share public`. */
export function parseZrokUrl(line: string): string | null {
return /https:\/\/[a-z0-9-]+\.share\.zrok\.io/i.exec(line)?.[0] ?? null;
}
/** HTTP origin constructed from the host and port reported by `bore local`. */
export function parseBoreUrl(line: string, fallbackHost?: string): string | null {
const match = /listening at (?:(?<host>[a-z0-9.-]+):)?(?<port>\d+)/i.exec(line);
const host = match?.groups?.host ?? fallbackHost;
const port = match?.groups?.port;
return host && port ? `http://${host}:${port}` : null;
}
function requireBinary(name: string): string {
const path = $which(name);
if (!path) {
throw new Error(`imageUrls exposure "${name}" requires the ${name} binary on PATH`);
}
return path;
}
function normalizeBaseUrl(url: string): string {
return url.replace(/\/+$/, "");
}
function boundedInteger(value: number | undefined, fallback: number, maximum: number): number {
if (value === undefined || !Number.isFinite(value)) return fallback;
return Math.min(maximum, Math.max(0, Math.floor(value)));
}
/**
* Verify that a public exposure reaches the local blob origin.
*
* Each request is cache-busted and time-bounded. Only the broker health
* endpoint's exact 204 response is accepted; errors expose only the sanitizedView on GitHub (pinned to 9690622007)
Solutions
- Install the required binary (e.g. `brew install cloudflared`, `apt install ngrok`) or download it from the vendor.
- Ensure the binary's directory is on PATH for the process running omp (check `echo $PATH` in that environment).
- Pick an exposure kind whose binary you already have — e.g. "ssh" if only ssh is installed, or "direct" which needs no binary.
- If omp runs as a daemon, export the full PATH in the service unit environment.
Example fix
# before: imageUrls destination "ngrok" without ngrok installed # after brew install ngrok # or apt/scoop equivalent export PATH="$PATH:/usr/local/bin" # or switch config to a binary-less kind: "direct"
Defensive patterns
Strategy: validation
Validate before calling
import { $which } from "@oh-my-pi/pi-utils";
const REQUIRED = { cloudflared: "cloudflared", ngrok: "ngrok", tailscale: "tailscale", devtunnel: "devtunnel", zrok: "zrok", bore: "bore", ssh: "ssh" };
if (!REQUIRED[kind] || !$which(REQUIRED[kind])) {
throw new Error(`exposure "${kind}" needs the ${REQUIRED[kind] ?? kind} binary installed and on PATH`);
} Try / catch
try {
await startExposure(config, port);
} catch (err) {
if (err instanceof Error && err.message.includes("binary on PATH")) {
logger.warn("tunnel binary missing; images will be inlined as base64", { kind: config.kind });
} else throw err;
} Prevention
- Install the tunnel CLI for your chosen destination before enabling it (brew/apt/scoop).
- Check $which(binary) at startup and warn early rather than failing mid-session.
- For headless/service launches, set an explicit PATH in the unit file or wrapper script.
- Prefer the "direct" kind in environments where you cannot install binaries.
When it happens
Trigger: Configuring imageUrls exposure kind "cloudflared"/"ngrok"/"tailscale"/"devtunnel"/"zrok"/"bore" (or ssh-based kinds) on a machine where that binary is not installed or not on the PATH of the omp process.
Common situations: Fresh machine or CI container missing tunnel CLIs; binary installed via version manager (homebrew, nvm-style paths) not present in the daemon's PATH; launching omp from a systemd unit or IDE with a minimal PATH.
Related errors
- Unable to resolve AWS credentials. Configure static environm
- blob broker worker requires ${BLOB_BROKER_SOCKET_ENV} and ${
- ${availability.reason ?? "Julia kernel unavailable"}
- ${availability.reason ?? "Julia kernel unavailable"}
- ${displayName} executable not found on PATH
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6bb667105c768ce1.
Report an issue: GitHub.