paperclipai/paperclip · error
Invalid sandbox environment variable key: ${key}
Error message
Invalid sandbox environment variable key: ${key} What it means
Thrown by buildLoginShellScript when an environment variable key passed in the caller env does not match /^[A-Za-z_][A-Za-z0-9_]*$/ (the POSIX shell identifier rule). Keys are validated before being injected into the login shell script to prevent shell injection via malformed variable names.
Source
Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:728
// Build the one-shot exec command. Daytona's `executeCommand` runs the script
// in a non-login shell, so it does not source `/etc/profile` on its own. The
// Daytona reference image puts `node`, `claude`, and the other CLIs on the PATH
// through `/etc/profile.d/00-restore-env.sh`, which only `/etc/profile` sources.
// So the wrapper sources the login profiles itself; a non-login shell is then
// enough to resolve the CLIs. The wrapper no longer sources `nvm.sh`; the
// sandbox image supplies `node` on the PATH. See the sandbox runtime
// requirements document.
function buildLoginShellScript(input: {
command: string;
args: string[];
cwd?: string;
env?: Record<string, string>;
stdinPath?: string;
}): string {
const callerEnv = input.env ?? {};
for (const key of Object.keys(callerEnv)) {
if (!isValidShellEnvKey(key)) {
throw new Error(`Invalid sandbox environment variable key: ${key}`);
}
}
// Caller env takes priority over noninteractive git credential defaults
const env = { ...NONINTERACTIVE_GIT_ENV, ...callerEnv };
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 redirectedCommand = input.stdinPath
? `${commandParts} < ${shellQuote(input.stdinPath)}`
: commandParts;
// Each `executeCommand` call runs in its own shell, so we don't `exec`-
// replace it; running the command as the last `&&`-chained line is enough to
// surface the right exit code.
const finalLine = envArgs.length > 0
? `env ${envArgs.join(" ")} ${redirectedCommand}`
: redirectedCommand;
const lines = [View on GitHub (pinned to 67001ec6eb)
Solutions
- Normalize env var keys to match ^[A-Za-z_][A-Za-z0-9_]*$ (letters, digits, underscore; not starting with a digit).
- Convert hyphens/dots to underscores before passing to the sandbox command env.
- Validate keys upstream (in the plugin caller) with the same regex before submission.
Example fix
// before: invalid key
env = { 'MY-VAR': 'x' }
// after: POSIX-safe key
env = { 'MY_VAR': 'x' } Defensive patterns
Strategy: validation
Validate before calling
const SHELL_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
function allEnvKeysValid(env: Record<string, string>): boolean {
return Object.keys(env).every((k) => SHELL_ENV_KEY.test(k));
} Type guard
function isValidShellEnvKey(value: string): boolean {
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
} Prevention
- Normalize env var keys to POSIX identifiers (letters, digits, underscore; leading non-digit) before passing to sandbox commands.
- Convert hyphens/dots to underscores when bridging from config/secret managers.
When it happens
Trigger: A command executed in the sandbox is given an env Record whose key contains characters outside [A-Za-z0-9_] or starts with a digit — e.g. 'MY-VAR', '1VAR', 'var.name', or an empty string.
Common situations: Caller passes a kebab-case or dotted env var name; a secret manager injects keys with hyphens/dots; misconfigured environment metadata with numeric-leading keys; copy-paste of YAML keys into env without normalization.
Related errors
- Daytona sync ${label} path is not a confined absolute path:
- Daytona sync ${label} path escapes the workspace remote dir:
- Daytona sandbox handle mismatch: handle ${sandbox.id} does n
- Environment variable ${envName} is empty or not set.
- Refusing to write export file outside output directory: ${re
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/65818980626ad75f.
Report an issue: GitHub.