paperclipai/paperclip · error
Invalid sandbox environment variable.
Error message
Invalid sandbox environment variable.
What it means
commandScript validates every entry in params.env: keys must match /^[A-Za-z_][A-Za-z0-9_]*$/ (a valid POSIX shell identifier) and values must be strings. Anything else — keys starting with a digit, containing dashes/dots/spaces, or non-string values — is rejected to guarantee the generated 'KEY=value' shell lines are safe.
Solutions
- Rename env keys to valid shell identifiers: letters, digits, underscores, not starting with a digit.
- Coerce values with String(value) before passing them in env.
- Drop or whitelist unsupported keys before calling execute, e.g. Object.fromEntries(entries.filter(([k, v]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(k) && typeof v === 'string')).
Example fix
// before
exec({ command: "make", env: { "BUILD-ID": 42 } })
// after
exec({ command: "make", env: { BUILD_ID: "42" } }) Defensive patterns
Strategy: validation
Validate before calling
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
function sanitizeEnv(env) {
return Object.fromEntries(
Object.entries(env ?? {}).filter(([k, v]) => ENV_KEY_RE.test(k) && typeof v === 'string')
.map(([k, v]) => [k, String(v)])
);
} Type guard
function isShellSafeEnv(env) {
return Object.entries(env ?? {}).every(([k, v]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(k) && typeof v === 'string');
} Prevention
- Whitelist the env vars you forward instead of passing process.env wholesale
- Coerce values to strings before passing structured data
- Keep env keys shell-identifier-safe by convention (no dashes or dots)
When it happens
Trigger: Calling execute with params.env containing a key like '1PATH', 'MY-VAR', 'my.var', or an empty key, or a value that is a number, boolean, object, or undefined.
Common situations: Passing through process.env from a context with odd vars (e.g. npm_lifecycle_event-like keys with dashes); passing structured config values (numbers/objects) without String() conversion; forwarding env maps from other tools with dotted keys.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- External chat action id is invalid
- Invalid project repository directory
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
- A reusable lease handoff requires an execution workspace…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/ed8393c75444f5ed.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/execute.ts:21
import { setTimeout as delay } from "node:timers/promises";
import type { PluginEnvironmentExecuteParams, PluginEnvironmentExecuteResult } from "@paperclipai/plugin-sdk";
import { CreateosApiError, CreateosClient, identifier, object } from "./client.js";
const MAX_LINE_BYTES = 1_048_576;
const MAX_CAPTURE_CHARS = 4_194_304;
export class CreateosCleanupError extends Error {}
export function shellQuote(value: string): string {
if (value.includes("\0")) throw new Error("Sandbox command values cannot contain NUL.");
return `'${value.replace(/'/g, `'"'"'`)}'`;
}
function commandScript(params: PluginEnvironmentExecuteParams, stdinPath: string | null): string {
if (!params.command) throw new Error("A sandbox command is required.");
const env = Object.entries(params.env ?? {}).map(([key, value]) => {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== "string") {
throw new Error("Invalid sandbox environment variable.");
}
return `${key}=${shellQuote(value)}`;
});
const command = [params.command, ...(params.args ?? [])].map(shellQuote).join(" ");
return [
params.cwd ? `cd -- ${shellQuote(params.cwd)} || exit` : "",
`exec env ${env.join(" ")} ${command}${stdinPath ? ` < ${shellQuote(stdinPath)}` : ""}`,
].filter(Boolean).join("\n");
}
async function* events(response: Response): AsyncGenerator<Record<string, unknown>> {
if (!response.body) throw new Error("CreateOS returned an empty process stream.");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let pending = "";
try {
for (;;) {
const { value, done } = await reader.read();View on GitHub (pinned to 3f1d897a7c)