paperclipai/paperclip · error · Error

Invalid sandbox environment variable key: ${key}

Error message

Invalid sandbox environment variable key: ${key}

What it means

Thrown by buildLoginShellScript in the E2B provider when any key of the input.env map fails the isValidShellEnvKey regex ^[A-Za-z_][A-Za-z0-9_]*$. The keys are emitted verbatim into an `exec env KEY=...` line of a login-shell wrapper, so an invalid shell identifier would either break the wrapper or inject shell syntax; the plugin rejects it up front.

Source

Thrown at packages/plugins/sandbox-providers/e2b/src/plugin.ts:170

  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
}

// Source the user's login profiles before exec so commands run with the same
// PATH the user sees in an interactive shell. e2b's `sandbox.commands.run`
// otherwise spawns a non-login, non-interactive shell whose PATH does not
// include npm-globals or anything else the template installs via
// .profile/.bashrc — which makes the hello probe fail with
// `exec: <cli>: not found` even when the binary is on disk. The wrapper no
// longer sources `nvm.sh`; the sandbox image supplies `node` on the PATH.
function buildLoginShellScript(input: {
  command: string;
  args: string[];
  env?: Record<string, string>;
}): string {
  const env = input.env ?? {};
  for (const key of Object.keys(env)) {
    if (!isValidShellEnvKey(key)) {
      throw new Error(`Invalid sandbox environment variable key: ${key}`);
    }
  }
  const envArgs = Object.entries(env)
    .filter((entry): entry is [string, string] => typeof entry[1] === "string")
    .map(([key, value]) => `${key}=${shellQuote(value)}`);
  const commandParts = [shellQuote(input.command), ...input.args.map(shellQuote)].join(" ");
  const execLine = envArgs.length > 0
    ? `exec env ${envArgs.join(" ")} ${commandParts}`
    : `exec ${commandParts}`;
  return [
    'if [ -f /etc/profile ]; then . /etc/profile >/dev/null 2>&1 || true; fi',
    'if [ -f "$HOME/.profile" ]; then . "$HOME/.profile" >/dev/null 2>&1 || true; fi',
    // .bash_profile typically sources .bashrc itself; only source .bashrc
    // directly when no .bash_profile exists to avoid re-running idempotency-
    // sensitive setup (nvm, PATH prepends) twice on templates that wire
    // .bash_profile -> .bashrc.
    'if [ -f "$HOME/.bash_profile" ]; then . "$HOME/.bash_profile" >/dev/null 2>&1 || true; elif [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc" >/dev/null 2>&1 || true; fi',
    'if [ -f "$HOME/.zprofile" ]; then . "$HOME/.zprofile" >/dev/null 2>&1 || true; fi',

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Sanitize env keys before passing them to the sandbox: replace non-[A-Za-z0-9_] chars with '_', uppercase, and strip leading digits.
  2. Drop env vars whose keys cannot be converted to valid shell identifiers instead of forwarding them.
  3. Validate the env map at config-ingest time so the bad key is reported at save, not at exec.

Example fix

// before
const env = { 'MY-VAR': 'x', '1ST': 'y' };

// after
function sanitizeEnvKey(k) {
  const cleaned = k.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
  return cleaned.replace(/^[0-9]+/, '');
}
const env = Object.fromEntries(
  Object.entries(raw).map(([k, v]) => [sanitizeEnvKey(k), v]).filter(([k]) => k)
);
Defensive patterns

Strategy: validation

Validate before calling

const SHELL_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
function sanitizeEnvForShell(env: Record<string, string>): Record<string, string> {
  const out: Record<string, string> = {};
  for (const [k, v] of Object.entries(env)) {
    const key = k.toUpperCase().replace(/[^A-Z0-9_]/g, '_').replace(/^[0-9]+/, '');
    if (key && SHELL_KEY.test(key)) out[key] = v;
  }
  return out;
}

Type guard

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

Try / catch

try {
  buildLoginShellScript({ command, args, env });
} catch (err) {
  if (err instanceof Error && /Invalid sandbox environment variable key/.test(err.message)) {
    // strip/sanitize offending keys and retry
    return buildLoginShellScript({ command, args, env: sanitizeEnvForShell(env) });
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing an env map to a sandbox exec/probe whose keys contain characters illegal in a POSIX shell variable name: dashes (MY-VAR), dots (my.var), leading digits (1ST), spaces, or non-ASCII.

Common situations: Driver config copied from a YAML/JSON that allowed arbitrary key names; user-supplied env vars forwarded without sanitization; keys like 'NODE_ENV' pass but 'npm-config-foo' or 'GIT.Committer' do not.

Related errors


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