nodejs/node · error · GypError
Variable %s must expand to a string or list of strings; foun
Error message
Variable %s must expand to a string or list of strings; found a %s
What it means
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.
Source
Thrown at tools/gyp/pylib/gyp/input.py:1030
for item in replacement:
if isinstance(item, bytes) and not isinstance(item, str):
item = item.decode("utf-8") # done on Python 3 only
if not contents[-1] == "/" and type(item) not in (str, int):
raise GypError(
"Variable "
+ contents
+ " must expand to a string or list of strings; "
+ "list contains a "
+ item.__class__.__name__
)
# Run through the list and handle variable expansions in it. Since
# the list is guaranteed not to contain dicts, this won't do anything
# with conditions sections.
ProcessVariablesAndConditionsInList(
replacement, phase, variables, build_file
)
elif type(replacement) not in (str, int):
raise GypError(
"Variable "
+ contents
+ " must expand to a string or list of strings; "
+ "found a "
+ replacement.__class__.__name__
)
if expand_to_list:
# Expanding in list context. It's guaranteed that there's only one
# replacement to do in |input_str| and that it's this replacement. See
# above.
if isinstance(replacement, list):
# If it's already a list, make a copy.
output = replacement[:]
else:
# Split it the same way sh would split arguments.
output = shlex.split(str(replacement))
else:View on GitHub (pinned to 1b2de5e052)
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.
Example fix
// before
'variables': { 'config': {'os': 'linux'} },
'type': '<(config)',
// after — expand a scalar field
'variables': { 'config_os': 'linux' },
'type': '<(config_os)', Defensive patterns
Strategy: type-guard
Validate before calling
def check_scalar_var(name, value):
assert type(value) in (str, int, list), \
f'{name} expands to unsupported type {type(value).__name__}' Type guard
def is_expandable_value(value) -> bool:
return type(value) in (str, int) or (isinstance(value, list) and all(type(x) in (str, int) for x in value)) Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Variable expansion in this context permits str and int only,
- Undefined variable %s in %s
- Variable %s must expand to a string or list of strings; list
- Variable expansion in this context permits str and int only,
- Variable expansion in this context permits strings and lists
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/72513dfecfd5350b.
Report an issue: GitHub.