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
- Filter out symbol keys and values when copying objects into process.env
- Convert values with String(v) for non-symbols (accepting the DEP0104 warning) and skip symbols
- Keep env writes at the process boundary; pass typed config objects internally
- 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
- Use Reflect.ownKeys and skip symbols when copying objects into env
- Never point generic assignment proxies at process.env
- Stringify env values at the edge and keep symbols in-process
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
- ERR_INVALID_OBJECT_DEFINE_PROPERTY
- Invalid URL: ${raw}
- ERR_INVALID_ARG_TYPE
- The bench name can't be empty
- Expected 'fn' field in the first argument to be a bench func
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/957c8f8285e4d584.
Report an issue: GitHub.