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

  1. Verify the printed path exists and is executable (ls -l, chmod +x if needed; use the .exe name on Windows)
  2. 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
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/003e8979182af076. Report an issue: GitHub.