headroomlabs-ai/headroom · error · Error
Service reachable at ${explicitUrl}, but it does not appear
Error message
Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? "unknown service"}). What it means
The OpenClaw proxy manager probes the explicitly configured proxyUrl, found the address reachable, but the service did not identify itself as a Headroom proxy (probe.isHeadroom false, with a reason string). This is a deliberate guard against silently routing LLM traffic through an unrelated HTTP service. The probe reason in the message tells you what kind of service actually answered.
Source
Thrown at plugins/openclaw/src/proxy-manager.ts:100
: null;
const defaultCandidates = this.getDefaultProxyCandidates(port);
const candidateUrls = explicitUrl ? [explicitUrl] : [...defaultCandidates];
const probeByUrl = new Map<string, ProxyProbeResult>();
for (const url of candidateUrls) {
const probe = await probeHeadroomProxy(url);
probeByUrl.set(url, probe);
if (probe.reachable && probe.isHeadroom) {
this.proxyUrl = url;
this.logger.info(`Headroom proxy already running at ${url}`);
return url;
}
}
if (explicitUrl) {
const explicitProbe = probeByUrl.get(explicitUrl);
if (explicitProbe?.reachable && !explicitProbe.isHeadroom) {
throw new Error(
`Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? "unknown service"}).`,
);
}
}
// Remote URLs are connect-only — never auto-start a subprocess for them
if (explicitUrl && !isLocalProxyUrl(explicitUrl)) {
throw new Error(
`Remote Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running at that address.`,
);
}
// Auto-start is only available for local proxies
if (this.config.autoStart === true) {
const startupUrl = explicitUrl ?? defaultCandidates[0];
const startupProbe = probeByUrl.get(startupUrl);
if (startupProbe?.reachable && !startupProbe.isHeadroom) {
throw new Error(View on GitHub (pinned to 322425c43b)
Solutions
- Check what is actually listening on that port (lsof -i :PORT / the probe's reason string) and either stop it or move it
- Point proxyUrl at the real Headroom proxy's actual address/port
- If Headroom is supposed to be there, verify it is fully started — a half-started proxy can respond but fail the Headroom probe; retry after startup completes
Example fix
# before: another service on 8787
proxyManager.configure({ proxyUrl: "http://127.0.0.1:8787" });
# after: point at the actual Headroom port
headroom proxy --port 8790 &
proxyManager.configure({ proxyUrl: "http://127.0.0.1:8790" }); Defensive patterns
Strategy: validation
Validate before calling
import { probeHeadroomProxy } from "./proxy-probe.js";
// Before wiring config, confirm the target really is Headroom
const probe = await probeHeadroomProxy("http://127.0.0.1:8787");
if (!probe.reachable || !probe.isHeadroom) {
throw new Error(`proxyUrl is not a Headroom proxy: ${probe.reason}`);
} Type guard
interface Probe { reachable: boolean; isHeadroom: boolean; reason?: string }
function isHeadroomEndpoint(probe: Probe | undefined | null): probe is Probe & { reachable: true; isHeadroom: true } {
return Boolean(probe && probe.reachable && probe.isHeadroom);
} Try / catch
try {
await manager.resolveProxyUrl();
} catch (e) {
if (e instanceof Error && e.message.includes("does not appear to be a Headroom proxy")) {
// wrong service on the port — surface the probe reason to the operator
throw new Error(`Wrong service on proxy port: ${e.message}`);
}
throw e;
} Prevention
- Probe and verify the proxy identity during deployment smoke tests, not just TCP reachability
- Reserve a dedicated port for Headroom so unrelated dev servers cannot squat on it
- Include the probe reason in alerting so on-call can see which service answered
When it happens
Trigger: Configuring proxyUrl to a port occupied by another HTTP service — a dev server, a different proxy, a metrics endpoint — while autoStart is not enabled. The probe succeeded at the TCP/HTTP level but the Headroom identification check (probeHeadroomProxy) failed.
Common situations: Default port 8787 already taken by another tool; stale config pointing at an old port where a different service now runs; container port-mapping mismatch where the host port maps to the wrong container port.
Related errors
- Headroom proxy startup is disabled
- Remote Headroom proxy not reachable at ${explicitUrl}. Ensur
- Cannot auto-start Headroom at ${startupUrl}: port is in use
- Headroom proxy not reachable at ${explicitUrl}. Ensure the p
- Headroom proxy not detected on default endpoints (${defaultC
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/581c482cad661559.
Report an issue: GitHub.