{"record":{"id":"72513dfecfd5350b","repo":"nodejs/node","slug":"variable-s-must-expand-to-a-string-or-list-of-str-72513d","errorCode":null,"errorMessage":"Variable %s must expand to a string or list of strings; found a %s","messagePattern":"Variable (.+?) must expand to a string or list of strings; found a (.+?)","errorType":"exception","errorClass":"GypError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":1030,"sourceCode":"            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 \"\n                + replacement.__class__.__name__\n            )\n\n        if expand_to_list:\n            # Expanding in list context.  It's guaranteed that there's only one\n            # replacement to do in |input_str| and that it's this replacement.  See\n            # above.\n            if isinstance(replacement, list):\n                # If it's already a list, make a copy.\n                output = replacement[:]\n            else:\n                # Split it the same way sh would split arguments.\n                output = shlex.split(str(replacement))\n        else:","sourceCodeStart":1012,"sourceCodeEnd":1048,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L1012-L1048","documentation":"After variable expansion, if the resolved value is neither a str, an int, nor a list (e.g. it is a dict, a bool, None, a float), GYP raises this GypError naming the variable and the value's class name. GYP's expansion model only yields scalar strings, ints, or lists of those; any other top-level type is unsupported. This is the scalar/non-list counterpart to error 486.","triggerScenarios":"A 'variables' entry is bound to a dict and referenced via '<(var)'; a programmatic caller injects variables with a bool/None value; a <!pymod_do_main whose DoMain returns a non-string (DoMain's return is str()'d, so this is rare from that path).","commonSituations":"Defining 'variables': {'foo': {'a': 1}} and later referencing '<(foo)' expecting a string; JSON config injection that yields nested dicts at the top level; confusion between a variables-section dict (which is itself a scope) and a value to expand.","solutions":["Change the variable's value to a str, int, or list of those — if it was a dict meant as a nested scope, move it into a nested 'variables' block instead of referencing it with <().","If you need a particular field, reference '<(foo.key)' or restructure so the expandable value is a scalar.","Audit where the variable is populated (programmatic API or .gypi include) and coerce the value type."],"exampleFix":"// before\n'variables': { 'config': {'os': 'linux'} },\n'type': '<(config)',\n// after — expand a scalar field\n'variables': { 'config_os': 'linux' },\n'type': '<(config_os)',","handlingStrategy":"type-guard","validationCode":"def check_scalar_var(name, value):\n    assert type(value) in (str, int, list), \\\n        f'{name} expands to unsupported type {type(value).__name__}'","typeGuard":"def is_expandable_value(value) -> bool:\n    return type(value) in (str, int) or (isinstance(value, list) and all(type(x) in (str, int) for x in value))","tryCatchPattern":null,"preventionTips":["Use nested 'variables' blocks for dict-valued scopes; never <()-expand a dict.","When injecting variables programmatically, coerce dicts/lists to scalar strings.","Reference scalar fields rather than whole dict variables."],"tags":["gyp","variables","type-check","build-config","expansion"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}