{"record":{"id":"3f5609a8bff1693a","repo":"nodejs/node","slug":"name-key-list-key-must-be-list-not-value-c","errorCode":null,"errorMessage":"{name} key {list_key} must be list, not {value.__class__.__name__} when applying {\"!\": \"exclusion\", \"/\": \"regex\"}[operation]","messagePattern":"(.+?) key (.+?) must be list, not (.+?) when applying (.+?)\\[operation\\]","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":2546,"sourceCode":"        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 \"\n                + value.__class__.__name__\n                + \" when applying \"\n                + {\"!\": \"exclusion\", \"/\": \"regex\"}[operation]\n            )\n\n        if list_key not in lists:\n            lists.append(list_key)\n\n    # Delete the lists that are known to be unneeded at this point.\n    for del_list in del_lists:\n        del the_dict[del_list]\n\n    for list_key in lists:\n        the_list = the_dict[list_key]","sourceCodeStart":2528,"sourceCodeEnd":2564,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L2528-L2564","documentation":"When a list-filter key ('sources!'/sources/') is itself a list, the BASE list it operates on ('sources') must also be a list. If the_dict[list_key] is present but not a list, gyp raises ValueError naming the base key, its actual type, and the operation (exclusion or regex).","triggerScenarios":"the_dict contains both a filter key (e.g. 'sources!') that is a valid list AND a base key 'sources' that is NOT a list (e.g. a string or None). Reached after the filter-key type check passes.","commonSituations":"'sources' accidentally set to a string while 'sources!' is a list; configuration merge that converted the base list to a scalar; refactoring that left a stale scalar base.","solutions":["Ensure the base key (the part before '!' or '/') is a list whenever a filter for it exists.","Convert the scalar to a single-element list or remove the filter.","Check merge/include order that may have overwritten the base."],"exampleFix":"// before: { 'sources': 'a.cc', 'sources!': ['a.cc'] }\n// after: { 'sources': ['a.cc', 'b.cc'], 'sources!': ['a.cc'] }","handlingStrategy":"type-guard","validationCode":"for k, v in the_dict.items():\n    if k[-1:] in ('!', '/') and isinstance(v, list):\n        base = k[:-1]\n        if base in the_dict:\n            assert isinstance(the_dict[base], list), '%s base must be list' % base\n","typeGuard":"def base_is_list_when_filtered(d, filter_key): return isinstance(d.get(filter_key[:-1]), list)","tryCatchPattern":"try:\n    gyp.main(args)\nexcept ValueError as e:\n    if 'must be list' in str(e) and 'when applying' in str(e): print('Make the base list a real list'); raise","preventionTips":["Ensure the base list is actually a list before adding '!'/ and '/' filters.","Don't let merges convert a list base to a scalar.","Lint list bases alongside their 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"}