{"record":{"id":"701e908faf5ae3e8","repo":"nodejs/node","slug":"variable-s-must-expand-to-a-string-or-list-of-str","errorCode":null,"errorMessage":"Variable %s must expand to a string or list of strings; list contains a %s","messagePattern":"Variable (.+?) must expand to a string or list of strings; list contains a (.+?)","errorType":"exception","errorClass":"GypError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":1016,"sourceCode":"                #   '>@(_sources!)',\n                # ],\n                # 'action/': [\n                #   '>@(_sources/)',\n                # ],\n                replacement = []\n            else:\n                raise GypError(\"Undefined variable \" + contents + \" in \" + build_file)\n        else:\n            replacement = variables[contents]\n\n        if isinstance(replacement, bytes) and not isinstance(replacement, str):\n            replacement = replacement.decode(\"utf-8\")  # done on Python 3 only\n        if isinstance(replacement, list):\n            for item in replacement:\n                if isinstance(item, bytes) and not isinstance(item, str):\n                    item = item.decode(\"utf-8\")  # done on Python 3 only\n                if not contents[-1] == \"/\" and type(item) not in (str, int):\n                    raise GypError(\n                        \"Variable \"\n                        + contents\n                        + \" must expand to a string or list of strings; \"\n                        + \"list contains a \"\n                        + item.__class__.__name__\n                    )\n            # Run through the list and handle variable expansions in it.  Since\n            # the list is guaranteed not to contain dicts, this won't do anything\n            # with conditions sections.\n            ProcessVariablesAndConditionsInList(\n                replacement, phase, variables, build_file\n            )\n        elif type(replacement) not in (str, int):\n            raise GypError(\n                \"Variable \"\n                + contents\n                + \" must expand to a string or list of strings; \"\n                + \"found a \"","sourceCodeStart":998,"sourceCodeEnd":1034,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L998-L1034","documentation":"When a variable expands to a list, GYP iterates the list and requires each item to be a str or int (unless the reference ends in '/', which permits dict-valued items). If any item is neither — e.g. a nested dict, a float, None — GYP raises this GypError naming the variable, the requirement, and the offending item's class name. The check guards the downstream ProcessVariablesAndConditionsInList call which assumes string-like elements.","triggerScenarios":"A 'variables' entry is a list containing a dict (e.g. [{'a':1}, 'b']) and is referenced via '<(var)'; a list containing None or a float produced by a <!() command whose stdout was parsed into a heterogeneous list; a programmatic gyp invocation that injects a list of mixed types.","commonSituations":"Accidentally nesting a dict inside a list-valued variable; a codegen command returning lines that get split into non-string tokens; building the variables dict dynamically from JSON/config that includes nested objects.","solutions":["Flatten or stringify the offending items so every element of the list is a str or int.","If dicts in the list are intentional, reference the variable with the '/' suffix form that allows them.","Audit the 'variables' dict and any <!() command output feeding the list to find the non-str/int element (the error names the class)."],"exampleFix":"// before\n'variables': { 'my_list': ['a', {'nested': 1}, 'b'] },\n'args': ['<(my_list)'],\n// after — keep the list homogeneous in strings\n'variables': { 'my_list': ['a', 'b', 'nested'] },","handlingStrategy":"type-guard","validationCode":"def check_list_var(name, value):\n    if isinstance(value, list):\n        bad = [type(x).__name__ for x in value if type(x) not in (str, int)]\n        assert not bad, f'{name} list contains non-str/int: {bad}'","typeGuard":"def is_str_int_list(value) -> bool:\n    return isinstance(value, list) and all(type(x) in (str, int) for x in value)","tryCatchPattern":null,"preventionTips":["Keep list-valued variables homogeneous (strings or ints only).","If dicts are needed inside a list, use the '/' suffix form that permits them.","Audit <!() command output before binding it to a list variable."],"tags":["gyp","variables","type-check","list","build-config"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}