{"record":{"id":"2699d1d8fdbe492f","repo":"nodejs/node","slug":"name-key-key-must-be-list-not-value-class","errorCode":null,"errorMessage":"{name} key {key} must be list, not {value.__class__.__name__}","messagePattern":"(.+?) key (.+?) must be list, not (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":2532,"sourceCode":"    # Look through the dictionary for any lists whose keys end in \"!\" or \"/\".\n    # These are lists that will be treated as exclude lists and regular\n    # expression-based exclude/include lists.  Collect the lists that are\n    # needed first, looking for the lists that they operate on, and assemble\n    # then into |lists|.  This is done in a separate loop up front, because\n    # the _included and _excluded keys need to be added to the_dict, and that\n    # can't be done while iterating through it.\n\n    lists = []\n    del_lists = []\n    for key, value in the_dict.items():\n        if not key:\n            continue\n        operation = key[-1]\n        if operation not in {\"!\", \"/\"}:\n            continue\n\n        if not isinstance(value, list):\n            raise ValueError(\n                name + \" key \" + key + \" must be list, not \" + value.__class__.__name__\n            )\n\n        list_key = key[:-1]\n        if list_key not in the_dict:\n            # This happens when there's a list like \"sources!\" but no corresponding\n            # \"sources\" list.  Since there's nothing for it to operate on, queue up\n            # the \"sources!\" list for deletion now.\n            del_lists.append(key)\n            continue\n\n        if not isinstance(the_dict[list_key], list):\n            value = the_dict[list_key]\n            raise ValueError(\n                name\n                + \" key \"\n                + list_key\n                + \" must be list, not \"","sourceCodeStart":2514,"sourceCodeEnd":2550,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L2514-L2550","documentation":"ProcessListFiltersInDict handles keys ending in '!' (exclusion) or '/' (regex). The value paired with such a key MUST be a list. If value is not a list, gyp raises ValueError (note: ValueError, not GypError) naming the key, its expected list type, and the actual class.","triggerScenarios":"A dict contains a key ending in '!' or '/' (e.g. 'sources!', 'sources/') whose value is not a Python list (e.g. a string, dict, or None).","commonSituations":"Typing 'sources!' as a string instead of a list; copy-paste that dropped the list brackets; generator emitting a scalar for an exclusion key; confusing the exclusion syntax with a single-item shorthand.","solutions":["Make the value of any '!'/''/' key a list, e.g. 'sources!': ['bad.cc']."],"exampleFix":"// before: 'sources!': 'generated.cc'\n// after: 'sources!': ['generated.cc']","handlingStrategy":"type-guard","validationCode":"for k, v in the_dict.items():\n    if k[-1:] in ('!', '/'):\n        assert isinstance(v, list), '%s must be a list' % k\n","typeGuard":"def filter_value_is_list(d, k): return isinstance(d.get(k), list)","tryCatchPattern":"try:\n    gyp.main(args)\nexcept ValueError as e:\n    if 'must be list' in str(e): print('Make the filter value a list'); raise","preventionTips":["Always use list brackets for '!'/ and '/' keys.","Lint exclusion/regex keys for list values.","Avoid scalar shorthand for filters."],"tags":["gyp","list-filter","validation","build-config"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}