JuliusBrussee/caveman · error · Error
${envVar} does not point to an executable: ${explicit}
Error message
${envVar} does not point to an executable: ${explicit} What it means
ensureBinary() first honors an explicit override: if the env var (CAVEMAN_MCP_BIN, CAVEMAN_SHRINK_BIN, or CAVEMAN_BROWSE_BIN depending on the tool) is set, its value must point at an executable file or setup fails immediately. This is a strict-contract check — an override that silently fell through to auto-download would hide operator intent.
Source
Thrown at packages/shared/binary-installer/installer.mjs:158
const reader = response.body.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
hash.update(value);
await file.write(value);
}
} finally {
reader.releaseLock();
await file.close();
}
return hash.digest("hex");
}
export async function ensureBinary({ name, envVar }) {
const explicit = process.env[envVar];
if (explicit) {
if (!executable(explicit)) throw new Error(`${envVar} does not point to an executable: ${explicit}`);
return explicit;
}
const found = onPath(name);
if (found) return found;
const binDir = join(process.env.CAVEMAN_HOME ?? join(homedir(), ".caveman"), "bin");
const target = join(binDir, binaryInstallFilename(name));
if (executable(target)) return target;
const { os, arch } = targetPlatform();
const artifact = `${name}_${os}_${arch}`;
const base = (process.env.CAVE_BINARY_RELEASE_BASE ?? BINARY_RELEASE_BASE_DEFAULT).replace(/\/+$/, "");
const release = `${base}/${BINARY_RELEASE}`;
const timeout = timeoutMs();
const [checksumsResponse, signatureResponse] = await Promise.all([
asset(`${release}/checksums.txt`, timeout),
asset(`${release}/checksums.txt.keysig`, timeout),
]);
const [checksums, signature] = await Promise.all([checksumsResponse.text(), signatureResponse.text()]);View on GitHub (pinned to 27d5a3981a)
Solutions
- Verify the printed path exists and is executable (ls -l, chmod +x if needed; use the .exe name on Windows)
- Fix or remove the env var so it either points at a real executable or is unset, letting PATH lookup and auto-install take over
- In containers, install or copy the binary in the same image stage that sets the env var
Example fix
# before export CAVEMAN_MCP_BIN=/usr/local/bin/caveman-mcp # not built there yet # after chmod +x /opt/caveman-mcp && export CAVEMAN_MCP_BIN=/opt/caveman-mcp # or simply: unset CAVEMAN_MCP_BIN
Defensive patterns
Strategy: validation
Validate before calling
const fs = require("node:fs");
const p = process.env.CAVEMAN_MCP_BIN; // or the SHRINK/BROWSE variant
if (p) {
let ok = false;
try { fs.accessSync(p, fs.constants.X_OK); ok = true; } catch {}
if (!ok) throw new Error(`${p} is not executable — chmod +x or unset CAVEMAN_MCP_BIN`);
} Type guard
function isExecutablePath(p) {
try { fs.accessSync(p, fs.constants.X_OK); return true; } catch { return false; }
} Try / catch
try { bin = await ensureBinary({ name, envVar }); }
catch (e) {
if (/does not point to an executable/.test(String(e?.message)) && process.env[envVar]) {
delete process.env[envVar]; // drop the stale override, let auto-discovery run
return ensureBinary({ name, envVar });
}
throw e;
} Prevention
- In Dockerfiles, COPY the binary and set its env var in the same stage after chmod +x
- Prefer putting the binary on PATH over absolute-path env overrides that rot
When it happens
Trigger: Setting the env var to a nonexistent path, a directory, a file without execute permission, a path with a typo, or a Windows path without .exe; also stale overrides pointing at deleted builds.
Common situations: Hardcoded absolute paths in Docker or compose files that break across image rebuilds; deploying to a new machine with copied env files; forgetting chmod +x on a manually placed binary; macOS quarantine dropping permissions after copy.
Related errors
- CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.st
- cave_vercel_terminal_failure
- caveman agent: invalid .caveman/provider.json
- cache-replay: verifier command must be an existing regular f
- cave_harness_aborted
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/003e8979182af076.
Report an issue: GitHub.