denoland/deno · error · TypeError

Cannot convert a Symbol value to a string

Error message

Cannot convert a Symbol value to a string

What it means

process.env is a Proxy whose set trap mimics V8 semantics: assigning with a symbol key or a symbol value forces ToString on a symbol, which throws TypeError('Cannot convert a Symbol value to a string'). Non-string, non-symbol values only trigger the DEP0104 deprecation warning; symbols are the one hard failure.

Source

Thrown at ext/node/polyfills/_process/process.ts:144

      return envValue;
    },
    ownKeys: () => ReflectOwnKeys(Deno.env.toObject()),
    getOwnPropertyDescriptor: (_target, name) => {
      const value = denoEnvGet(String(name));
      if (value !== undefined) {
        return {
          __proto__: null,
          enumerable: true,
          configurable: true,
          value,
        };
      }
    },
    set(_target, prop, value) {
      // Match Node: v8 ToString on a symbol key or value throws TypeError.
      if (typeof prop === "symbol" || typeof value === "symbol") {
        throw new TypeError("Cannot convert a Symbol value to a string");
      }

      if (typeof value !== "string") {
        nodeProcess ??= loadProcess();
        nodeProcess.emitWarning(
          "Assigning any value other than a string, number, or boolean to a " +
            "process.env property is deprecated. Please make sure to convert the value " +
            "to a string before setting process.env with it.",
          "DeprecationWarning",
          "DEP0104",
        );
      }

      Deno.env.set(String(prop), String(value));
      return true; // success
    },
    has: (target, prop) => {
      if (typeof prop === "symbol") {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Filter out symbol keys and values when copying objects into process.env
  2. Convert values with String(v) for non-symbols (accepting the DEP0104 warning) and skip symbols
  3. Keep env writes at the process boundary; pass typed config objects internally
  4. Audit generic assignment proxies that target process.env

Example fix

// before
for (const key of Reflect.ownKeys(userEnv)) {
  process.env[key] = userEnv[key]; // symbol key throws
}

// after
for (const key of Reflect.ownKeys(userEnv)) {
  if (typeof key === 'symbol') continue;
  const v = userEnv[key];
  if (typeof v === 'symbol') continue;
  process.env[key] = String(v);
}
Defensive patterns

Strategy: type-guard

Validate before calling

function setEnv(key, value) {
  if (typeof key === 'symbol' || typeof value === 'symbol') return false;
  process.env[key] = String(value);
  return true;
}

Type guard

const isEnvWritable = (k, v) => typeof k !== 'symbol' && typeof v !== 'symbol';

Try / catch

try {
  process.env[k] = v;
} catch (e) {
  if (e instanceof TypeError && /Symbol/.test(e.message)) {
    // skip symbol keys/values when copying
  } else throw e;
}

Prevention

When it happens

Trigger: process.env[Symbol('KEY')] = 'x'; process.env.KEY = Symbol('value'); Reflect.set(process.env, sym, 'v'); generic copy loops that write every Reflect.ownKeys() entry (including symbols) into process.env.

Common situations: Libraries that enumerate objects with Reflect.ownKeys and copy them into env; constant tables keyed by symbols; test doubles assigning symbol-valued properties; metaprogramming that forwards descriptors onto process.env.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/957c8f8285e4d584. Report an issue: GitHub.