Context
On a Hetzner VPS, Claude Code is installed at /root/.local/bin/claude and works from an interactive SSH shell, but QuadWork's setup wizard shows "Claude Code (not installed)". The user had to manually ln -s /root/.local/bin/claude /usr/local/bin/claude to work around it.
Root cause
isCliInstalled() in server/index.js:48-55 uses execFileSync("which", [cmd]) to detect CLIs. Claude Code's installer adds the binary to ~/.local/bin/ and appends a PATH entry to ~/.bashrc/~/.profile. But Node's execFileSync doesn't source shell profile files — it only sees the PATH inherited at process start. In headless/VPS environments where the server is started via npx, ~/.local/bin is often missing from the inherited PATH.
// Current — fails when CLI is in ~/.local/bin
function isCliInstalled(cmd) {
try {
execFileSync("which", [cmd], { encoding: "utf-8", stdio: "pipe" });
return true;
} catch {
return false;
}
}
Fix
Add fallback path checks when which fails. Check common install locations:
const os = require("os");
function isCliInstalled(cmd) {
try {
execFileSync("which", [cmd], { encoding: "utf-8", stdio: "pipe" });
return true;
} catch {
// Fallback: check common install locations that may not be in PATH
const fallbacks = [
path.join(os.homedir(), ".local", "bin", cmd),
path.join(os.homedir(), ".npm-global", "bin", cmd),
`/usr/local/bin/${cmd}`,
];
return fallbacks.some((p) => fs.existsSync(p));
}
}
Acceptance criteria
Environment
- Hetzner VPS, Debian/Ubuntu, root user
- Claude Code installed via official installer →
~/.local/bin/claude
- Node 24.x, started via
npx quadwork start
Context
On a Hetzner VPS, Claude Code is installed at
/root/.local/bin/claudeand works from an interactive SSH shell, but QuadWork's setup wizard shows "Claude Code (not installed)". The user had to manuallyln -s /root/.local/bin/claude /usr/local/bin/claudeto work around it.Root cause
isCliInstalled()inserver/index.js:48-55usesexecFileSync("which", [cmd])to detect CLIs. Claude Code's installer adds the binary to~/.local/bin/and appends a PATH entry to~/.bashrc/~/.profile. But Node'sexecFileSyncdoesn't source shell profile files — it only sees the PATH inherited at process start. In headless/VPS environments where the server is started vianpx,~/.local/binis often missing from the inherited PATH.Fix
Add fallback path checks when
whichfails. Check common install locations:Acceptance criteria
isCliInstalled("claude")returns true when claude is at~/.local/bin/claudeeven if not in PATHisCliInstalled("codex")same fallback behaviornpm run buildpassesEnvironment
~/.local/bin/claudenpx quadwork start