.env value cut short at '#': unquoted JSON or a hex color like COLOR=#ea580c reads as empty or truncated (Node --env-file, dotenv)
In an .env file, an unquoted value ends at the first '#', even with no space before it. So COLOR=#ea580c parses as an empty string, and USERS=[{"color":"#2563eb"}] is cut to `[{"color":"`, which then fails as "Unterminated string in JSON". Node's own parser (--env-file, process.loadEnvFile, util.parseEnv) and the dotenv package behave the same. Fix: single- or double-quote any value that contains '#'.
What happens
In an .env file, an unquoted value stops at the first #. The rest of the line is treated as a comment. There does not need to be a space before the #.
Reproduced with both parsers that Node apps commonly use:
- Node's built-in parser:
util.parseEnv, which is the same code asnode --env-fileandprocess.loadEnvFile. - The
dotenvpackage. Next.js loads.env*files through it, via@next/env.
Input:
A=#ea580c
B=[{"c":"#fff"}]
C='#ok'
D=plain # comment
E="#dq"
Both parsers return:
{"A":"","B":"[{\"c\":\"","C":"#ok","D":"plain","E":"#dq"}
How it shows up
- A hex color setting is silently empty, so the app falls back to its default color. Nothing reports an error.
- A JSON setting containing a color or a URL fragment is cut off.
JSON.parsethen throwsUnterminated string in JSON at position N, where N is where the first#was. - It is easy to miss because a shell
sourceof the same file may behave differently. Each tool reads the line its own way.
Fix
Quote any value that contains #:
COLOR='#ea580c'
USERS='[{"name":"a","color":"#2563eb"}]'
Single quotes are simplest for JSON, because the value's own double quotes then need no escaping.
A check that can fail
To find candidates, flag every assignment whose value does not start with a quote and contains a # with no whitespace before it. A # after whitespace is usually an intended trailing comment.
/^[[:space:]]*(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_.-]*[[:space:]]*=/ {
v=$0; sub(/^[^=]*=[[:space:]]*/,"",v); q=substr(v,1,1)
h=index(v,"#")
if (q!="\"" && q!="'" && h>0 && (h==1 || substr(v,h-1,1) !~ /[[:space:]]/)) print FILENAME": "$0
}
It prints whole lines, so don't run it where the output is logged and the file holds secrets. In that case print only the key name.
You can also test the parse itself:
import { parseEnv } from "node:util";
JSON.parse(parseEnv(fs.readFileSync(".env.example", "utf8")).USERS);
This throws if the value was cut.
Conditions
- node
- 24.22.1
- dotenv
- 17.4.2
- next_env
- 16.4.3
- os
- Ubuntu 24.04
- date
- 2026-10-29
Sources
- https://nodejs.org/api/environment_variables.html — Node's dotenv-format documentation: comments start with #; quoting rules for values
- https://github.com/motdotla/dotenv#comments — dotenv README: values containing # must be wrapped in quotes