nodejs/node · error · Exception
Unexpected error while reading
Error message
Unexpected error while reading %s: %s
What it means
Thrown by LoadPythonDictionary in gypi_to_gn.py when Python's eval() of the .gypi file's text raises any exception other than SyntaxError (SyntaxError is re-raised separately with the filename attached). The eval runs with __builtins__ set to None, so the file must be a pure literal dict. The message embeds the offending file path and the str() of the original exception, so the underlying cause is preserved in the message text.
Solutions
- Read the parenthesized cause in the message (the second %s) — it is str(original_exception), so it names the exact problem and often the line.
- Open the .gypi file and confirm it is a single Python literal dictionary: keys are strings, values are literals, no function calls or bare name references, no use of disabled builtins.
- If the file is generated, regenerate it from its gyp source rather than editing it by hand.
- If you must call LoadPythonDictionary programmatically, pre-validate the file with ast.literal_eval in a scratch check first.
Example fix
// before (offending .gypi contents)
{
'variables': {
'list': sorted(['b','a']), # NameError-like: call not allowed in sandbox
},
}
// after
{
'variables': {
'list': ['a','b'],
},
} Defensive patterns
Strategy: validation
Validate before calling
import ast
def is_safe_gypi(path):
src = open(path).read()
try:
ast.literal_eval(src) # rejects calls / names
return True
except (ValueError, SyntaxError):
return False Try / catch
try:
data = LoadPythonDictionary(path)
except Exception as e:
# message is 'Unexpected error while reading <path>: <cause>'
cause = str(e).rsplit(': ', 1)[-1]
raise SystemExit('bad .gypi %s: %s' % (path, cause)) Prevention
- Keep .gypi files as pure literal dicts — no function calls or name references.
- Treat .gypi as generated artifacts; regenerate from gyp rather than editing by hand.
- Pre-validate with ast.literal_eval before invoking the build step.
When it happens
Trigger: Invoking `gypi_to_gn.py <file>` (or calling LoadPythonDictionary directly) where <file> contains a Python expression that eval rejects at evaluation time rather than parse time: referencing an undefined name (NameError), calling a function, accessing a builtin like len/range/True-as-builtin (builtins are disabled), arithmetic that fails (ZeroDivisionError), or any object whose repr/eval round-trips badly.
Common situations: A .gypi file was hand-edited and now references a variable or calls a function; the file references True/False/None through a name that resolves to something unexpected under the sandboxed eval; a stale or corrupt .gypi produced by a buggy gyp generation step; pasting content that includes a trailing expression or macro call.
Related errors
- config_path must be a JSON file containing a dictionary
- Could not automatically locate src directory. This isa…
- Could not find pattern matching
- --depfile requires --stamp and vice versa
- --depfile requires --stamp and vice versa
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/3bbc3e7779f5a7f3.
Report an issue: GitHub.
Appendix: source
Thrown at tools/gypi_to_gn.py:228
def TranslateToGnChars(s):
for code in s.encode('utf-8'):
if code in (34, 36, 92): # For '"', '$', or '\\'.
yield '\\' + chr(code)
elif 32 <= code < 127:
yield chr(code)
else:
yield '$0x%02X' % code
def LoadPythonDictionary(path):
file_string = open(path).read()
try:
file_data = eval(file_string, {'__builtins__': None}, None)
except SyntaxError as e:
e.filename = path
raise
except Exception as e:
raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
# Flatten any variables to the top level.
if 'variables' in file_data:
file_data.update(file_data['variables'])
del file_data['variables']
# Strip all elements that this script can't process.
elements_to_strip = [
'conditions',
'direct_dependent_settings',
'target_conditions',
'target_defaults',
'targets',
'includes',
'actions',
]View on GitHub (pinned to 1b2de5e052)