{"record":{"id":"581c482cad661559","repo":"headroomlabs-ai/headroom","slug":"service-reachable-at-expliciturl-but-it-does-n","errorCode":null,"errorMessage":"Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? \"unknown service\"}).","messagePattern":"Service reachable at (.+?), but it does not appear to be a Headroom proxy \\((.+?)\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/openclaw/src/proxy-manager.ts","lineNumber":100,"sourceCode":"      : null;\n    const defaultCandidates = this.getDefaultProxyCandidates(port);\n    const candidateUrls = explicitUrl ? [explicitUrl] : [...defaultCandidates];\n    const probeByUrl = new Map<string, ProxyProbeResult>();\n\n    for (const url of candidateUrls) {\n      const probe = await probeHeadroomProxy(url);\n      probeByUrl.set(url, probe);\n      if (probe.reachable && probe.isHeadroom) {\n        this.proxyUrl = url;\n        this.logger.info(`Headroom proxy already running at ${url}`);\n        return url;\n      }\n    }\n\n    if (explicitUrl) {\n      const explicitProbe = probeByUrl.get(explicitUrl);\n      if (explicitProbe?.reachable && !explicitProbe.isHeadroom) {\n        throw new Error(\n          `Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? \"unknown service\"}).`,\n        );\n      }\n    }\n\n    // Remote URLs are connect-only — never auto-start a subprocess for them\n    if (explicitUrl && !isLocalProxyUrl(explicitUrl)) {\n      throw new Error(\n        `Remote Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running at that address.`,\n      );\n    }\n\n    // Auto-start is only available for local proxies\n    if (this.config.autoStart === true) {\n      const startupUrl = explicitUrl ?? defaultCandidates[0];\n      const startupProbe = probeByUrl.get(startupUrl);\n      if (startupProbe?.reachable && !startupProbe.isHeadroom) {\n        throw new Error(","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/plugins/openclaw/src/proxy-manager.ts#L82-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: another service on 8787\nproxyManager.configure({ proxyUrl: \"http://127.0.0.1:8787\" });\n\n# after: point at the actual Headroom port\nheadroom proxy --port 8790 &\nproxyManager.configure({ proxyUrl: \"http://127.0.0.1:8790\" });","handlingStrategy":"validation","validationCode":"import { probeHeadroomProxy } from \"./proxy-probe.js\";\n\n// Before wiring config, confirm the target really is Headroom\nconst probe = await probeHeadroomProxy(\"http://127.0.0.1:8787\");\nif (!probe.reachable || !probe.isHeadroom) {\n  throw new Error(`proxyUrl is not a Headroom proxy: ${probe.reason}`);\n}","typeGuard":"interface Probe { reachable: boolean; isHeadroom: boolean; reason?: string }\n\nfunction isHeadroomEndpoint(probe: Probe | undefined | null): probe is Probe & { reachable: true; isHeadroom: true } {\n  return Boolean(probe && probe.reachable && probe.isHeadroom);\n}","tryCatchPattern":"try {\n  await manager.resolveProxyUrl();\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"does not appear to be a Headroom proxy\")) {\n    // wrong service on the port — surface the probe reason to the operator\n    throw new Error(`Wrong service on proxy port: ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["proxy","configuration","port-conflict","openclaw"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}