JuliusBrussee/caveman · error · Error
does not point to an executable
Error message
${envVar} does not point to an executable: ${explicit} What it means
`ensureBinary({ name, envVar })` first honors an explicit override: if `process.env[envVar]` is set, that path must exist and be executable (checked by `executable()`). If it isn't, the installer throws immediately instead of silently ignoring the override and downloading. This catches misconfigured overrides early with the offending path in the message.
Solutions
- Run `ls -l "$CAVEMAN_<NAME>_BIN"` and confirm the file exists and has the x bit.
- Fix the env var to the real binary path, or `unset` it so ensureBinary falls back to PATH lookup/download.
- If the file exists but isn't executable: `chmod +x <path>`.
- If the binary was removed by an upgrade, reinstall it or let the installer download it by unsetting the override.
Example fix
// before export CAVEMAN_RIPGREP_BIN=/opt/tools/rg-14/bin # directory, not executable // after export CAVEMAN_RIPGREP_BIN=/opt/tools/rg-14/bin/rg && chmod +x "$CAVEMAN_RIPGREP_BIN"
Defensive patterns
Strategy: validation
Validate before calling
import { accessSync, constants } from "node:fs";
function isExecutable(p) {
try { accessSync(p, constants.F_OK | constants.X_OK); return true; } catch { return false; }
}
const v = process.env.CAVEMAN_RIPGREP_BIN;
if (v && !isExecutable(v)) throw new Error(`CAVEMAN_RIPGREP_BIN not executable: ${v}`); Try / catch
try {
await ensureBinary({ name, envVar });
} catch (e) {
if (String(e.message).includes("does not point to an executable")) {
delete process.env[envVar]; // fall back to PATH lookup / download
return ensureBinary({ name, envVar });
}
throw e;
} Prevention
- Validate env-var paths in shell profiles at login (test -x).
- chmod +x binaries after copying; use `COPY --chmod=755` in Dockerfiles.
- Unset stale overrides after upgrading/relocating tools.
- Point overrides at files, never directories.
When it happens
Trigger: Calling `ensureBinary({ name, envVar })` when `process.env[envVar]` is set to a path that is missing, a directory, or lacks the execute bit — e.g. `CAVEMAN_RIPGREP_BIN=/opt/rg/bin` (directory), a deleted binary, a Windows-style path on Linux, or a file that was never `chmod +x`.
Common situations: Hand-edited shell profile pointing at a path that moved after an upgrade; copied binary without preserving permissions; Docker image copied binaries with `COPY` losing exec bits; pointing at a wrapper script that isn't executable.
Understand the failure class
Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.
Related errors
- CAVE_SETUP_TIMEOUT must be a positive integer
- caveman agent: multiple provider credentials found; set…
- caveman build: set CAVE_MODEL when zero or multiple…
- Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or…
- multiple provider credentials detected; pass --provider
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/003e8979182af076.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/binary-installer/installer.mjs:178
// exits within a second or two of being asked to.
async function replaceWithRetry(part, target, attempts = 6) {
for (let attempt = 0; ; attempt++) {
try {
renameSync(part, target);
return;
} catch (error) {
const locked = error.code === "EPERM" || error.code === "EBUSY" ||
error.code === "EACCES" || error.code === "ETXTBSY";
if (!locked || attempt >= attempts - 1) throw error;
await new Promise((done) => setTimeout(done, 100 * 2 ** attempt));
}
}
}
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 3ee70a1026)