{"record":{"id":"3bbc3e7779f5a7f3","repo":"nodejs/node","slug":"unexpected-error-while-reading-s-s","errorCode":null,"errorMessage":"Unexpected error while reading %s: %s","messagePattern":"Unexpected error while reading (.+?): (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"tools/gypi_to_gn.py","lineNumber":228,"sourceCode":"def TranslateToGnChars(s):\n  for code in s.encode('utf-8'):\n    if code in (34, 36, 92):  # For '\"', '$', or '\\\\'.\n      yield '\\\\' + chr(code)\n    elif 32 <= code < 127:\n      yield chr(code)\n    else:\n      yield '$0x%02X' % code\n\n\ndef LoadPythonDictionary(path):\n  file_string = open(path).read()\n  try:\n    file_data = eval(file_string, {'__builtins__': None}, None)\n  except SyntaxError as e:\n    e.filename = path\n    raise\n  except Exception as e:\n    raise Exception(\"Unexpected error while reading %s: %s\" % (path, str(e)))\n\n  assert isinstance(file_data, dict), \"%s does not eval to a dictionary\" % path\n\n  # Flatten any variables to the top level.\n  if 'variables' in file_data:\n    file_data.update(file_data['variables'])\n    del file_data['variables']\n\n  # Strip all elements that this script can't process.\n  elements_to_strip = [\n    'conditions',\n    'direct_dependent_settings',\n    'target_conditions',\n    'target_defaults',\n    'targets',\n    'includes',\n    'actions',\n  ]","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gypi_to_gn.py#L210-L246","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (offending .gypi contents)\n{\n  'variables': {\n    'list': sorted(['b','a']),  # NameError-like: call not allowed in sandbox\n  },\n}\n// after\n{\n  'variables': {\n    'list': ['a','b'],\n  },\n}","handlingStrategy":"validation","validationCode":"import ast\ndef is_safe_gypi(path):\n    src = open(path).read()\n    try:\n        ast.literal_eval(src)        # rejects calls / names\n        return True\n    except (ValueError, SyntaxError):\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    data = LoadPythonDictionary(path)\nexcept Exception as e:\n    # message is 'Unexpected error while reading <path>: <cause>'\n    cause = str(e).rsplit(': ', 1)[-1]\n    raise SystemExit('bad .gypi %s: %s' % (path, cause))","preventionTips":["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."],"tags":["build","gyp","config","eval"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}