Version
LifeOS 7.40.4 (repo main at 5e2f2e8) / installer (Tools/InstallEngine.ts) and LIFEOS/TOOLS/ClipSource.ts
What is broken
Two code paths look up binaries with POSIX-only mechanisms, so on native Windows they report tools as missing that are installed and on PATH.
- Installer, always broken on Windows.
detectTool() and detectHarness() run command -v <name> through tryExec(), which calls execSync with no shell option. On win32 that is cmd.exe, which has no command built-in, so every probe fails. This happens even when the installer runs inside Claude Code's Bash tool or a hook, because execSync still uses cmd.exe there. DetectEnv then reports bun and git as installed: false and the harness as confidence: "assumed", although bun is the very runtime executing it. The Setup workflow branches on these fields by name, so an installing agent is told to install prerequisites that are present, and harness detection degrades from a binary match to a guess.
- ClipSource, broken outside Git Bash.
requireBinary() runs Bun.spawnSync(["which", bin]). Windows has no which.exe, so outside a Git Bash environment (PowerShell, cmd, a Windows scheduled task) spawnSync throws ENOENT and the tool dies with Executable not found in $PATH: "which" while ffmpeg and ffprobe are installed. Inside Git Bash, which comes from Git's usr/bin and the check passes.
Point 1 was noted in passing in a comment on #1730, which closed on the symlink fix; it never had its own issue. The Doctor-side Windows probes (PATH split on :, no PATHEXT, no Windows browser paths) are #1810/#1992, already fixed for the next release. On this machine Doctor 7.40.4 misses gh, ffmpeg and Chrome for that reason, and that is not re-reported here.
Where (file:line)
LifeOS/Tools/InstallEngine.ts:112: detectTool() runs tryExec(`command -v ${name}`)
LifeOS/Tools/InstallEngine.ts:136: detectHarness() hasBin runs tryExec(`command -v ${c.bin}`)
LifeOS/Tools/InstallEngine.ts:84-86: tryExec() uses execSync(cmd, { timeout: 5000, stdio: [...] }), default shell (cmd.exe on win32). The same file ships as install/skills/LifeOS/Tools/InstallEngine.ts.
LifeOS/install/LIFEOS/TOOLS/ClipSource.ts:138: requireBinary() runs Bun.spawnSync(["which", bin]), called at :200-202 for ffmpeg, ffprobe and yt-dlp
Repro on a clean tree
# Windows 11, PowerShell. bun, git, Claude Code and ffmpeg installed and on PATH.
git clone https://github.com/danielmiessler/LifeOS lifeos-clean
cd lifeos-clean/LifeOS
# 1. Installer
bun Tools/DetectEnv.ts
# Observed: "bun": { "installed": false }, "git": { "installed": false }, "harness": { ..., "confidence": "assumed" }
# 2. ClipSource (the binary checks run before the source is opened; a dummy path is enough)
bun install/LIFEOS/TOOLS/ClipSource.ts C:\temp\no-such-video.mp4 --start 0 --end 5
# Observed: ClipSource: Executable not found in $PATH: "which"
# Same shell, same machine, the tools are there:
bun -e "console.log(Bun.which('bun'), Bun.which('git'), Bun.which('claude'), Bun.which('ffmpeg'))"
# Observed: four resolved paths (claude resolves to claude.cmd from npm)
cmd /c "command -v bun"
# Observed: 'command' is not recognized as an internal or external command, operable program or batch file.
Negative control
On unpatched 7.40.4 (main 5e2f2e8), Windows 11 Pro 10.0.26200, bun 1.4.2, Git for Windows 2.52.0, Claude Code 2.1.291 (npm), ffmpeg 8.0.1 (winget), from a clean clone:
DetectEnv prints bun: {"installed":false}, git: {"installed":false}, harness.confidence: "assumed".
ClipSource.ts from PowerShell stops with Executable not found in $PATH: "which". The same command from Git Bash passes the binary checks and stops at source file not found for the dummy path.
- Probing ffmpeg, gh and git four ways in the same environments:
| Probe |
From PowerShell |
From Git Bash |
execSync("command -v X") (InstallEngine) |
not found |
not found |
Bun.spawnSync(["which", X]) (ClipSource) |
throws ENOENT |
found |
Bun shell $`which X` |
found |
found |
Bun.which(X) |
found |
found |
Suggested fix
Resolve binaries with Bun.which(name), which is cross-platform and PATHEXT-aware (it finds claude.cmd). Use it in detectTool(), in detectHarness()'s hasBin, and in ClipSource.requireBinary(). The version probes (bun --version, git --version) already work through cmd.exe and can stay. This is the direction #1810 took for Doctor.ts. Untested as a patch; the table above is the evidence that Bun.which resolves every binary in both environments.
Related, not included because not demonstrated in isolation: install/skills/Webdesign/Tools/VerifyDesign.ts:33 and DriveClaudeDesign.ts:18 use the same Bun.spawnSync(["which", "interceptor"]). Outside Git Bash, VerifyDesign fails earlier on a spawned mkdir -p (:142), so its which call is never reached there.
Before submitting
Version
LifeOS 7.40.4 (repo
mainat 5e2f2e8) / installer (Tools/InstallEngine.ts) andLIFEOS/TOOLS/ClipSource.tsWhat is broken
Two code paths look up binaries with POSIX-only mechanisms, so on native Windows they report tools as missing that are installed and on PATH.
detectTool()anddetectHarness()runcommand -v <name>throughtryExec(), which callsexecSyncwith noshelloption. On win32 that iscmd.exe, which has nocommandbuilt-in, so every probe fails. This happens even when the installer runs inside Claude Code's Bash tool or a hook, becauseexecSyncstill usescmd.exethere.DetectEnvthen reportsbunandgitasinstalled: falseand the harness asconfidence: "assumed", although bun is the very runtime executing it. The Setup workflow branches on these fields by name, so an installing agent is told to install prerequisites that are present, and harness detection degrades from a binary match to a guess.requireBinary()runsBun.spawnSync(["which", bin]). Windows has nowhich.exe, so outside a Git Bash environment (PowerShell, cmd, a Windows scheduled task)spawnSyncthrows ENOENT and the tool dies with Executable not found in $PATH: "which" whileffmpegandffprobeare installed. Inside Git Bash,whichcomes from Git'susr/binand the check passes.Point 1 was noted in passing in a comment on #1730, which closed on the symlink fix; it never had its own issue. The Doctor-side Windows probes (PATH split on
:, no PATHEXT, no Windows browser paths) are #1810/#1992, already fixed for the next release. On this machine Doctor 7.40.4 missesgh,ffmpegand Chrome for that reason, and that is not re-reported here.Where (file:line)
LifeOS/Tools/InstallEngine.ts:112:detectTool()runstryExec(`command -v ${name}`)LifeOS/Tools/InstallEngine.ts:136:detectHarness()hasBinrunstryExec(`command -v ${c.bin}`)LifeOS/Tools/InstallEngine.ts:84-86:tryExec()usesexecSync(cmd, { timeout: 5000, stdio: [...] }), default shell (cmd.exeon win32). The same file ships asinstall/skills/LifeOS/Tools/InstallEngine.ts.LifeOS/install/LIFEOS/TOOLS/ClipSource.ts:138:requireBinary()runsBun.spawnSync(["which", bin]), called at :200-202 forffmpeg,ffprobeandyt-dlpRepro on a clean tree
Negative control
On unpatched 7.40.4 (
main5e2f2e8), Windows 11 Pro 10.0.26200, bun 1.4.2, Git for Windows 2.52.0, Claude Code 2.1.291 (npm), ffmpeg 8.0.1 (winget), from a clean clone:DetectEnvprintsbun: {"installed":false},git: {"installed":false},harness.confidence: "assumed".ClipSource.tsfrom PowerShell stops withExecutable not found in $PATH: "which". The same command from Git Bash passes the binary checks and stops atsource file not foundfor the dummy path.execSync("command -v X")(InstallEngine)Bun.spawnSync(["which", X])(ClipSource)$`which X`Bun.which(X)Suggested fix
Resolve binaries with
Bun.which(name), which is cross-platform and PATHEXT-aware (it findsclaude.cmd). Use it indetectTool(), indetectHarness()'shasBin, and inClipSource.requireBinary(). The version probes (bun --version,git --version) already work throughcmd.exeand can stay. This is the direction #1810 took forDoctor.ts. Untested as a patch; the table above is the evidence thatBun.whichresolves every binary in both environments.Related, not included because not demonstrated in isolation:
install/skills/Webdesign/Tools/VerifyDesign.ts:33andDriveClaudeDesign.ts:18use the sameBun.spawnSync(["which", "interceptor"]). Outside Git Bash, VerifyDesign fails earlier on a spawnedmkdir -p(:142), so itswhichcall is never reached there.Before submitting