JuliusBrussee/caveman · error

cave_sandbox_credential_capability_ambiguous

cave_sandbox_credential_capability_ambiguous

Error message

cave_sandbox_credential_capability_ambiguous

What it means

Thrown by validateSandboxCredentialEnv() when the requested env names are individually allowlisted but map to more than one provider capability (capabilities.size > 1). The sandbox builds one deterministic child env with exactly one selected provider capability, so mixing credential names from two different providers (e.g. one OpenAI name and one Anthropic name) in one call is ambiguous and refused.

Source

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

export function validateSandboxCredentialEnv(names: readonly string[]): void {
  const capabilities = new Set<SandboxCredentialCapability>();
  for (const name of names) {
    const denied = typeof name === "string" &&
      (SANDBOX_CREDENTIAL_DENY_NAMES.has(name) ||
        SANDBOX_CREDENTIAL_DENY_PREFIXES.some((prefix) => name.startsWith(prefix)));
    const capability = denied || typeof name !== "string"
      ? undefined
      : SANDBOX_CREDENTIAL_ENV_TO_CAPABILITY.get(name);
    if (capability === undefined) {
      // Keep profile-controlled names out of errors. This is a policy result,
      // not a diagnostic surface, and the name may itself identify a secret.
      throw new Error("cave_sandbox_credential_env_not_allowlisted");
    }
    capabilities.add(capability);
  }
  if (capabilities.size > 1) {
    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");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Split the call: pass only the env names of the single provider the tool run actually uses, one buildSandboxToolEnv call per capability
  2. Derive the names from one provider's entry in SANDBOX_CREDENTIAL_ENV_BY_CAPABILITY rather than hand-listing
  3. Group your profile by provider at config time so mixed lists are impossible to construct

Example fix

// before
buildSandboxToolEnv([...openAiNames, ...anthropicNames]);

// after
const openAiEnv = buildSandboxToolEnv(openAiNames);
const anthropicEnv = buildSandboxToolEnv(anthropicNames); // separate tool runs
Defensive patterns

Strategy: validation

Validate before calling

function namesShareOneCapability(names: readonly string[]): boolean {
  const caps = new Set(names.map((n) => capabilityOf(n)).filter((c) => c !== undefined));
  return caps.size <= 1; // capabilityOf mirrors SANDBOX_CREDENTIAL_ENV_TO_CAPABILITY.get
}

Try / catch

try { buildSandboxToolEnv(names); } catch (e) { if (e instanceof Error && e.message === 'cave_sandbox_credential_capability_ambiguous') throw new SandboxConfigError('env names span multiple providers; split per provider', { cause: e }); throw e; }

Prevention

When it happens

Trigger: Calling buildSandboxToolEnv(['<openai-capability-name>', '<anthropic-capability-name>']) — any combination where at least two names resolve to different keys of SANDBOX_CREDENTIAL_ENV_BY_CAPABILITY.

Common situations: Aggregating 'all creds we might need' into one env list; a multi-provider tool profile that forwards every configured provider's variables at once; concatenating two provider profiles during a migration.

Related errors


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