nodejs/node · error · Exception

Unexpected error while reading %s: %s

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.

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)

Solutions

  1. 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.
  2. 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.
  3. If the file is generated, regenerate it from its gyp source rather than editing it by hand.
  4. 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

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


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/3bbc3e7779f5a7f3. Report an issue: GitHub.