paperclipai/paperclip · error · Error

exe.dev environments require an API key in config or EXE_API

Error message

exe.dev environments require an API key in config or EXE_API_KEY.

What it means

Thrown by resolveApiKey in the exe.dev provider when neither config.apiKey nor process.env.EXE_API_KEY yields a non-empty string. resolveApiKey is called inside runLifecycleCommand on every POST to the exe.dev API (Authorization: Bearer <key>), so the error surfaces on any lease/probe/lifecycle call.

Source

Thrown at packages/plugins/sandbox-providers/exe-dev/src/plugin.ts:272

    integrations: parseStringArray(raw.integrations),
    tags: parseStringArray(raw.tags),
    setupScript: parseOptionalString(raw.setupScript),
    prompt: parseOptionalString(raw.prompt),
    timeoutMs: Number.isFinite(timeoutMs) ? Math.trunc(timeoutMs) : DEFAULT_TIMEOUT_MS,
    reuseLease: raw.reuseLease === true,
    sshUser: parseOptionalString(raw.sshUser),
    sshPrivateKey: parseOptionalString(raw.sshPrivateKey),
    sshIdentityFile: parseOptionalString(raw.sshIdentityFile),
    sshPort: Number.isFinite(sshPort) ? Math.trunc(sshPort) : 22,
    strictHostKeyChecking: parseOptionalString(raw.strictHostKeyChecking) ?? "accept-new",
  };
}

function resolveApiKey(config: ExeDevDriverConfig): string {
  if (config.apiKey) return config.apiKey;
  const envApiKey = process.env.EXE_API_KEY?.trim() ?? "";
  if (!envApiKey) {
    throw new Error("exe.dev environments require an API key in config or EXE_API_KEY.");
  }
  return envApiKey;
}

function isValidShellEnvKey(value: string): boolean {
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
}

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, `'"'"'`)}'`;
}

function formatErrorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

function buildVmName(config: ExeDevDriverConfig, params: PluginEnvironmentAcquireLeaseParams): string {
  const envPart = params.environmentId.replace(/[^a-z0-9]+/gi, "").slice(0, 8).toLowerCase() || "env";

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Export EXE_API_KEY (note: EXE_API_KEY, not EXEDEV_API_KEY) in the worker environment.
  2. Set apiKey in the exe.dev driver config blob.
  3. Confirm the variable name and that the value survives trim; check the secret mount in the deployment.

Example fix

# before
export EXEDEV_API_KEY=...   # wrong name

# after
export EXE_API_KEY=...       # correct name
# or in driver config:
# { "apiKey": "...", "apiUrl": "https://api.exe.dev/command" }
Defensive patterns

Strategy: validation

Validate before calling

function ensureExeKey(config: { apiKey?: string | null }): string {
  const fromEnv = process.env.EXE_API_KEY?.trim() ?? '';
  const key = (config.apiKey && config.apiKey.trim()) || fromEnv;
  if (!key) throw new Error('EXE_API_KEY (or config.apiKey) must be set');
  return key;
}

Type guard

function hasExeKey(config: { apiKey?: string | null }): boolean {
  return Boolean((config.apiKey && config.apiKey.trim()) || (process.env.EXE_API_KEY && process.env.EXE_API_KEY.trim()));
}

Try / catch

try {
  await plugin.onEnvironmentProbe(params);
} catch (err) {
  if (err instanceof Error && /EXE_API_KEY/.test(err.message)) {
    return { ok: false, error: 'exe.dev API key not configured' };
  }
  throw err;
}

Prevention

When it happens

Trigger: Any exe.dev provider operation invoked with config.apiKey unset/blank (parseOptionalString nulls blank values) AND process.env.EXE_API_KEY unset/blank/whitespace-only.

Common situations: Worker deployed without EXE_API_KEY in its env; secret injected under a different name (e.g. EXEDEV_API_KEY); config block for exe.dev missing the apiKey field; CI run that never sourced the secrets file.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/2bf668dc08287544. Report an issue: GitHub.