commit deedcc220eae0174745937d337bdea476aa0ea09
parent 1b5646deb8487d0517c3265976e3dd7ed86fd1c3
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 12:47:16 -0400
doctor: a worker's default engine binary comes from the report's Paths (L7)
A worker with no `bin` override was probed at app.defaultBin(), which reads
this process's getPaths(), not the Paths the doctor was handed — the same
in production, wrong under injected paths (release 11 review L7). Now
whisper-cpp takes paths.whisperBin and parakeet paths.parakeetBin, the way
the model beside it takes paths.whisperModel / paths.parakeetModel; chough
has no Paths field and keeps its app default.
doctor.test.ts +1; the test Paths gain whisperBin and parakeetBin.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/common/bin/doctor.test.ts b/common/bin/doctor.test.ts
@@ -53,7 +53,9 @@ function checkout(): { root: string; bin: string; paths: Paths } {
udisksctlBin: "udisksctl",
claudeBin: "claude",
diarizeBin: path.join(root, "scripts", "diarize.mjs"),
+ whisperBin: "whisper-cli",
whisperModel: path.join(root, "models", "ggml-base.en.bin"),
+ parakeetBin: path.join(root, "scripts", "parakeet-stitch.mjs"),
parakeetModel: "",
parakeetCliBin: "parakeet-cli",
} as unknown as Paths;
@@ -167,6 +169,34 @@ test("an enabled whisper worker needs its engine and its model — beside a corp
assert.deepEqual(tree(c.root), before);
});
+test("a worker with no bin override is checked against the report's Paths, not the process's", async () => {
+ // release 11 review L7: the default engine came from app.defaultBin(), i.e.
+ // THIS process's getPaths() — "whisper-cli" here — whatever Paths the doctor
+ // was handed. Now it is the handed Paths' whisperBin / parakeetBin, as the
+ // model beside it always was.
+ const c = checkout();
+ const p = c.paths as { whisperBin: string; parakeetBin: string };
+ p.whisperBin = "whisper-from-paths";
+ p.parakeetBin = "parakeet-from-paths";
+ writeFileSync(
+ c.paths.settingsFile,
+ JSON.stringify({
+ workers: [
+ { id: "w1", name: "CPU", kind: "local", enabled: true, priority: 0, appId: "whisper-cpp", config: {} },
+ { id: "w2", name: "GPU", kind: "local", enabled: true, priority: 1, appId: "parakeet", config: {} },
+ ],
+ }),
+ );
+ fake(c.bin, "whisper-from-paths");
+ fake(c.bin, "parakeet-from-paths");
+ const r = await run(c);
+ const detail = (id: string) => r.checks.find((x) => x.id === id)?.detail ?? "";
+ assert.equal(status(r, "engine:w1"), "ok", renderDoctorReport(r));
+ assert.match(detail("engine:w1"), /whisper-from-paths/);
+ assert.equal(status(r, "engine:w2"), "ok", renderDoctorReport(r));
+ assert.match(detail("engine:w2"), /parakeet-from-paths/);
+});
+
test("an override naming a binary that is not there fails even when nothing needs it", async () => {
const c = checkout();
const r = await run(c, { YTDLP_BIN: "/nonexistent/yt-dlp" });
diff --git a/common/bin/doctor.ts b/common/bin/doctor.ts
@@ -198,7 +198,12 @@ export async function collectDoctorReport(deps: DoctorDeps): Promise<DoctorRepor
const cfg = w.config ?? {};
const { getTranscriptionApp } = await import("../lib/transcriptionApps");
const app = getTranscriptionApp(w.appId);
- const bin = cfg.bin?.trim() || app.defaultBin();
+ // The default engine from the Paths this report is about, as the model
+ // below: `app.defaultBin()` reads the process's own getPaths() (release
+ // 11 review L7). chough has no Paths field; its default stays the app's.
+ const defaultBin =
+ w.appId === "whisper-cpp" ? paths.whisperBin : w.appId === "parakeet" ? paths.parakeetBin : app.defaultBin();
+ const bin = cfg.bin?.trim() || defaultBin;
// An engine is LOOKED UP, not run: a transcription engine's CLI has no
// cheap version flag, and running one to find out is not a doctor's call.
const id = `engine:${w.id}`;