mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 02:52:15 +00:00
fix(tools-manager): require clean exit in commandExists
commandExists only checked spawnSync's result.error, so a binary that spawns but exits non-zero (e.g. one broken by a GLIBC / shared-lib mismatch after a system upgrade) was misreported as available. getToolPath then returned the broken command name and ensureTool skipped its auto-install fallback, leaving the agent without a working tool and no auto-recovery. Aligns commandExists with the sibling runExtractionCommand, which already requires !result.error && result.status === 0, and adds a 5s spawn timeout so a hung PATH entry cannot block startup. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -155,3 +155,30 @@ describe("ensureTool", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getToolPath exit-status handling", () => {
|
||||
it("treats a binary that spawns but exits non-zero as missing", async () => {
|
||||
const { getToolPath } = await import("./tools-manager.js");
|
||||
// execve succeeded (no result.error) but the child exited non-zero — the
|
||||
// signature of an installed-but-broken binary (GLIBC / shared-lib mismatch).
|
||||
// Must not be reported as available, or ensureTool skips its download path.
|
||||
spawnSyncMock.mockReturnValue({
|
||||
error: undefined,
|
||||
status: 1,
|
||||
stderr: Buffer.alloc(0),
|
||||
stdout: Buffer.alloc(0),
|
||||
});
|
||||
expect(getToolPath("fd")).toBeNull();
|
||||
});
|
||||
|
||||
it("reports a binary present when it spawns and exits 0", async () => {
|
||||
const { getToolPath } = await import("./tools-manager.js");
|
||||
spawnSyncMock.mockReturnValue({
|
||||
error: undefined,
|
||||
status: 0,
|
||||
stderr: Buffer.alloc(0),
|
||||
stdout: Buffer.alloc(0),
|
||||
});
|
||||
expect(getToolPath("fd")).toBe("fd");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,9 +101,12 @@ const TOOLS: Record<string, ToolConfig> = {
|
||||
// Check if a command exists in PATH by trying to run it
|
||||
function commandExists(cmd: string): boolean {
|
||||
try {
|
||||
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
|
||||
// Check for ENOENT error (command not found)
|
||||
return result.error === undefined || result.error === null;
|
||||
const result = spawnSync(cmd, ["--version"], { stdio: "pipe", timeout: 5_000 });
|
||||
// Require a clean exit, not just a successful spawn. An installed-but-broken
|
||||
// binary (e.g. GLIBC mismatch after a system upgrade, missing shared lib)
|
||||
// spawns fine but exits non-zero; without the status check it would be
|
||||
// misreported as available and block ensureTool's auto-install fallback.
|
||||
return !result.error && result.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user