From 9208592f41ed920c32206c78fcf43c8d7db77f22 Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <1705004398@qq.com> Date: Tue, 14 Jul 2026 20:34:57 +0000 Subject: [PATCH] fix(doctor): honor explicit Hopper launcher --- src/application/Doctor.ts | 38 +++++++++++++++++++++++++------------- tests/doctor.test.ts | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/application/Doctor.ts b/src/application/Doctor.ts index 0226aaa98..2cfa7e63d 100644 --- a/src/application/Doctor.ts +++ b/src/application/Doctor.ts @@ -121,14 +121,16 @@ export const runDoctor = async ( }, ), ); - const candidates = [ - host.configuredHopperPath, - DEFAULT_HOPPER, - SYSTEM_LINUX_HOPPER, - linuxHopperLauncherPath(homedir()), - ...(await host.manualHopperPaths()), - await host.brewHopperPath(), - ].filter((value): value is string => value !== undefined); + const candidates = + host.configuredHopperPath === undefined + ? [ + DEFAULT_HOPPER, + SYSTEM_LINUX_HOPPER, + linuxHopperLauncherPath(homedir()), + ...(await host.manualHopperPaths()), + await host.brewHopperPath(), + ].filter((value): value is string => value !== undefined) + : [host.configuredHopperPath]; let hopperPath: string | undefined; for (const candidate of new Set(candidates)) if (await host.executable(candidate)) { @@ -136,11 +138,21 @@ export const runDoctor = async ( break; } checks.push( - check("hopper", hopperPath !== undefined, hopperPath, { - remediation: - "Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.", - classification: "missing_analysis_engine", - }), + check( + "hopper", + hopperPath !== undefined, + hopperPath ?? host.configuredHopperPath, + { + remediation: + host.configuredHopperPath === undefined + ? "Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH." + : "Unset or update HOPPER_LAUNCHER_PATH to an executable Hopper launcher.", + classification: + host.configuredHopperPath === undefined + ? "missing_analysis_engine" + : "config_drift", + }, + ), ); if (host.platform === "linux" && hopperPath !== undefined) checks.push( diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index f9d0ab02f..e7ed68d2c 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -60,6 +60,25 @@ describe("doctor", () => { expect(result.healthy).toBe(true); }); + it("does not fall back when the configured Hopper launcher is invalid", async () => { + const configuredPath = "/invalid/custom/hopper"; + const result = await runDoctor( + undefined, + host({ configuredHopperPath: configuredPath }), + ); + + expect(result.hopperPath).toBeUndefined(); + expect(result.healthy).toBe(false); + expect(result.checks.find(({ name }) => name === "hopper")).toEqual({ + name: "hopper", + ok: false, + classification: "config_drift", + detail: configuredPath, + remediation: + "Unset or update HOPPER_LAUNCHER_PATH to an executable Hopper launcher.", + }); + }); + it("distinguishes stale skills, path shadowing, and unobservable live state", async () => { const result = await runDoctor( undefined,