{"record":{"id":"66e45dfc1857bb5e","repo":"headroomlabs-ai/headroom","slug":"cannot-auto-start-headroom-at-startupurl-port","errorCode":null,"errorMessage":"Cannot auto-start Headroom at ${startupUrl}: port is in use by a non-Headroom service (${startupProbe.reason ?? \"unknown service\"}).","messagePattern":"Cannot auto-start Headroom at (.+?): port is in use by a non-Headroom service \\((.+?)\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/openclaw/src/proxy-manager.ts","lineNumber":118,"sourceCode":"        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(\n          `Cannot auto-start Headroom at ${startupUrl}: port is in use by a non-Headroom service (${startupProbe.reason ?? \"unknown service\"}).`,\n        );\n      }\n\n      this.logger.info(\n        `No Headroom proxy detected${explicitUrl ? ` at ${startupUrl}` : \" on default local endpoints\"}; attempting to auto-start...`,\n      );\n      await this.startHeadroomProxy(startupUrl, port);\n\n      const startedProbe = await waitForHeadroomProxy(\n        startupUrl,\n        this.config.startupTimeoutMs ?? 20_000,\n      );\n      if (startedProbe.reachable && startedProbe.isHeadroom) {\n        this.proxyUrl = startupUrl;\n        this.logger.info(`Headroom proxy started and reachable at ${startupUrl}`);\n        return startupUrl;\n      }","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/plugins/openclaw/src/proxy-manager.ts#L100-L136","documentation":"Auto-start is enabled, and the port the manager wants to launch Headroom on (explicit local proxyUrl or the default candidate, default port 8787) is already occupied by a service that is not Headroom. Rather than clobbering an unrelated process or launching a Headroom instance that cannot bind, the manager aborts with the occupying service's identity in the reason string.","triggerScenarios":"config.autoStart === true, and the startup URL's port responds to the probe as reachable-but-not-Headroom. Typical when a previous non-Headroom dev server, another proxy, or an orphaned process holds the default port.","commonSituations":"Long-lived dev server sharing the 8787 default; a crashed earlier run left a zombie listener; two tools configured with the same default port; another developer's service on a shared machine.","solutions":["Identify and stop the occupying process: lsof -i :8787 (or the port in the message), then kill it","Or configure a different port for Headroom via proxyPort / a different local proxyUrl","If the occupying service is actually a stale Headroom instance that fails the probe, kill it so a clean one can start"],"exampleFix":"// before: autoStart on default port 8787 occupied by a dev server\nmanager.configure({ autoStart: true });\n\n// after: move Headroom to a free port\nmanager.configure({ autoStart: true, proxyPort: 8790 });","handlingStrategy":"validation","validationCode":"import { probeHeadroomProxy } from \"./proxy-probe.js\";\n\nasync function portFreeForHeadroom(url: string): Promise<boolean> {\n  const probe = await probeHeadroomProxy(url);\n  // Safe to auto-start only when nothing answers, or Headroom itself is there\n  return !probe.reachable || probe.isHeadroom;\n}\n\nif (!await portFreeForHeadroom(startupUrl)) {\n  throw new Error(`Port occupied by a non-Headroom service — free it or change proxyPort`);\n}","typeGuard":"interface Probe { reachable: boolean; isHeadroom: boolean }\n\nfunction canAutoStart(probe: Probe | undefined): boolean {\n  return !probe || !probe.reachable || probe.isHeadroom;\n}","tryCatchPattern":"try {\n  await manager.resolveProxyUrl();\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"port is in use by a non-Headroom service\")) {\n    // deterministic conflict: configure a different proxyPort instead of retrying\n    manager.configure({ proxyPort: nextFreePort() });\n    await manager.resolveProxyUrl();\n  } else throw e;\n}","preventionTips":["Give Headroom a non-default dedicated port in shared/CI environments","Run a port pre-check in deployment scripts before enabling autoStart","Clean up zombie listeners from previous runs as part of the environment bootstrap"],"tags":["proxy","port-conflict","auto-start","openclaw"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}