From 993f56215c9d869313f9c98908f29b11d799e245 Mon Sep 17 00:00:00 2001 From: Kagura Chen Date: Tue, 24 Mar 2026 06:56:54 +0800 Subject: [PATCH] fix(onboard): use sudo lsof fallback to identify root-owned port blockers (fixes #726) Non-root users cannot see root-owned listeners with lsof, causing the onboard preflight to fall through to the net probe which can only report EADDRINUSE without process details. Users then see a generic 'Could not identify the process' message suggesting 'lsof -i :PORT' which also returns empty without sudo. Changes: - preflight.js: When lsof returns empty, retry with 'sudo lsof' to identify root-owned listeners (e.g., docker-proxy, leftover gateway) before falling through to the net probe - onboard.js: Update the user-facing hint to suggest 'sudo lsof' instead of plain 'lsof' --- bin/lib/onboard.js | 2 +- bin/lib/preflight.js | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index ace8d6e97..81079ddf7 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -414,7 +414,7 @@ async function preflight() { console.error(" systemctl --user stop openclaw-gateway.service"); } else { console.error(` Could not identify the process using port ${port}.`); - console.error(` Run: lsof -i :${port} -sTCP:LISTEN`); + console.error(` Run: sudo lsof -i :${port} -sTCP:LISTEN`); } console.error(""); console.error(` Detail: ${portCheck.reason}`); diff --git a/bin/lib/preflight.js b/bin/lib/preflight.js index 7f191413d..03d680d6d 100644 --- a/bin/lib/preflight.js +++ b/bin/lib/preflight.js @@ -57,7 +57,30 @@ async function checkPortAvailable(port, opts) { } // Empty lsof output is not authoritative — non-root users cannot // see listeners owned by root (e.g., docker-proxy, leftover gateway). - // Fall through to the net probe which uses bind() at the kernel level. + // Retry with sudo to identify root-owned listeners before falling + // through to the net probe (which can only detect EADDRINUSE but not + // the owning process). + if (dataLines.length === 0 && !o.lsofOutput) { + const sudoOut = runCapture( + `sudo lsof -i :${p} -sTCP:LISTEN -P -n 2>/dev/null`, + { ignoreError: true } + ); + if (typeof sudoOut === "string") { + const sudoLines = sudoOut.split("\n").filter((l) => l.trim()); + const sudoData = sudoLines.filter((l) => !l.startsWith("COMMAND")); + if (sudoData.length > 0) { + const parts = sudoData[0].split(/\s+/); + const proc = parts[0] || "unknown"; + const pid = parseInt(parts[1], 10) || null; + return { + ok: false, + process: proc, + pid, + reason: `sudo lsof reports ${proc} (PID ${pid}) listening on port ${p}`, + }; + } + } + } } }