JuliusBrussee/caveman · critical

cave_sandbox_credential_missing

cave_sandbox_credential_missing

Error message

cave_sandbox_credential_missing

What it means

Thrown by buildSandboxToolEnv() when a name passed the allowlist validation but process.env does not currently define it. The sandbox child env is built from an exact, deterministic baseline plus only the requested credential values — no process.env spread — so a validated name with no value in the parent environment fails closed rather than silently producing a child missing its credential.

Source

Thrown at packages/agent/src/runtime.ts:624

    throw new Error("cave_sandbox_credential_capability_ambiguous");
  }
}

/** Build the complete environment for an isolated tool child. No spread of
 * `process.env`: only deterministic runtime baseline plus an exact provider
 * capability selected by the validated live profile. */
export function buildSandboxToolEnv(names: readonly string[] = []): NodeJS.ProcessEnv {
  validateSandboxCredentialEnv(names);
  const env: NodeJS.ProcessEnv = {
    LANG: process.env.LANG ?? "C",
    LC_ALL: process.env.LC_ALL ?? "C",
    PATH: process.env.PATH ?? "",
    TZ: process.env.TZ ?? "UTC",
    CAVE_EVAL_FIXTURE: "1",
  };
  for (const name of names) {
    const value = process.env[name];
    if (value === undefined) throw new Error("cave_sandbox_credential_missing");
    env[name] = value;
  }
  return env;
}

interface InternalRunOptions extends RunOptions {
  lockedBuild?: CaveBuildLock;
  candidatePlan?: CavePlan;
  /**
   * Resolved once per root run and handed to descendants so a nested agent
   * neither re-probes the gateway nor disagrees with its parent about whether
   * this run is optimized or observe-only.
   */
  caveRoute?: ResolvedCaveRoute;
}

export type ResolvedCaveRoute = {
  readonly useGateway: boolean;

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Export the allowlisted variable in the environment that actually starts the process (CI secret variable, systemd Environment=, dotenv loaded before the run)
  2. Verify presence without printing the value: names.every(n => typeof process.env[n] === 'string') before calling buildSandboxToolEnv
  3. If the secret lives in a secrets manager, fetch it into the child env via the framework's supported credential path instead of relying on inherited shell env

Example fix

// before
const env = buildSandboxToolEnv(names); // ANTHROPIC-style allowlisted name not exported in CI

// after
const missing = names.filter((n) => process.env[n] === undefined);
if (missing.length) throw new Error(`credential env not set: count=${missing.length}`); // fail with context, never print values
const env2 = buildSandboxToolEnv(names);
Defensive patterns

Strategy: validation

Validate before calling

function assertCredentialsPresent(names: readonly string[]): void {
  const missing = names.filter((n) => process.env[n] === undefined);
  if (missing.length > 0) throw new Error(`${missing.length} allowlisted credential env var(s) are not set in this environment`); // never log names or values
}

Try / catch

try { const env = buildSandboxToolEnv(names); } catch (e) { if (e instanceof Error && e.message === 'cave_sandbox_credential_missing') throw new SandboxEnvError('credential env missing: check CI secrets / dotenv before agent start', { cause: e }); throw e; }

Prevention

When it happens

Trigger: Calling buildSandboxToolEnv(['<allowlisted-name>']) when that variable is unset in the current process — e.g. the credential was never exported, was exported in a different shell, or the runner (CI, service manager) strips it.

Common situations: Local run works (var in .bashrc) but CI fails because the secret is only in CI's masked-secret store under a different name; dotenv not loaded before the agent starts; the credential is provided via the framework's devkey/secret store rather than the raw environment.

Related errors


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