mastra-ai/mastra · error

Invalid environment variable name: "${key}"

Error message

Invalid environment variable name: "${key}"

What it means

buildLaunchScript generates a POSIX shell script that exports each env var before starting the server. Keys are validated against /^[A-Za-z_][A-Za-z0-9_]*$/ because they're interpolated directly into `export KEY=...`; an invalid key is rejected to prevent broken or malicious shell output.

Source

Thrown at deployers/sandbox/src/engine.ts:196

 */
export function buildLaunchScript(opts: { remoteDir: string; port: number; env: Record<string, string> }): string {
  const lines = ['#!/bin/sh', `cd ${shellQuote(opts.remoteDir)}`];

  // MASTRA_AUTO_DETECT_URL so Studio connects to the sandbox's public URL
  // (same origin) instead of localhost:4111 — overridable. PORT and
  // MASTRA_HOST are applied AFTER custom env: networking (`getPortUrl`) and
  // health checks target the configured port, and the server must bind
  // 0.0.0.0 to be reachable through the public port proxy. Change the port
  // via the deploy `port` option, not env.
  const env: Record<string, string> = {
    MASTRA_AUTO_DETECT_URL: 'true',
    ...opts.env,
    PORT: String(opts.port),
    MASTRA_HOST: '0.0.0.0',
  };
  for (const [key, value] of Object.entries(env)) {
    if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
      throw new Error(`Invalid environment variable name: "${key}"`);
    }
    lines.push(`export ${key}=${shellQuote(value)}`);
  }

  lines.push(`echo $$ > ${shellQuote(SERVER_PIDFILE)}`);
  lines.push(`exec node index.mjs >> ${shellQuote(SERVER_LOGFILE)} 2>&1`);
  return lines.join('\n') + '\n';
}

/** Create a gzipped tarball of the directory contents (excluding node_modules). */
export async function createTarball(dir: string): Promise<Buffer> {
  const tmp = await mkdtemp(join(tmpdir(), 'mastra-sandbox-'));
  const tarPath = join(tmp, 'deploy.tgz');
  try {
    await execFileAsync('tar', ['-czf', tarPath, '--exclude=node_modules', '-C', dir, '.']);
    return await readFile(tarPath);
  } finally {
    await rm(tmp, { recursive: true, force: true });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Rename the offending env key to a valid identifier (letters, digits, underscores; not starting with a digit)
  2. Sanitize env maps before passing: strip/convert illegal characters (dots/dashes → underscores)
  3. Validate the keys in your config loading step before calling the deploy
  4. Check the error message for which exact key was rejected

Example fix

// before
await deployToSandbox(sandbox, { env: { 'MY-VAR': 'x' } });
// after
await deployToSandbox(sandbox, { env: { MY_VAR: 'x' } });
Defensive patterns

Strategy: validation

Validate before calling

const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
for (const key of Object.keys(env)) {
  if (!ENV_KEY_RE.test(key)) throw new Error(`invalid env key: ${key}`);
}

Prevention

When it happens

Trigger: Passing env keys that are not valid shell identifiers — e.g. containing dashes, dots, spaces, digits at the start, or empty keys — via the env option to buildLaunchScript (used by deployToSandbox's launchScript).

Common situations: Parsing env files with odd keys into the env option; passing metadata-like keys (e.g. 'my-var' or 'app.name') instead of valid env names; programmatic env maps built from untrusted input.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/b1506d4a23ef3c76. Report an issue: GitHub.