nodejs/node · error · GypError
Variable %s must expand to a string or list of strings; list
Error message
Variable %s must expand to a string or list of strings; list contains a %s
What it means
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.
Source
Thrown at tools/gyp/pylib/gyp/input.py:1016
# '>@(_sources!)',
# ],
# 'action/': [
# '>@(_sources/)',
# ],
replacement = []
else:
raise GypError("Undefined variable " + contents + " in " + build_file)
else:
replacement = variables[contents]
if isinstance(replacement, bytes) and not isinstance(replacement, str):
replacement = replacement.decode("utf-8") # done on Python 3 only
if isinstance(replacement, list):
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 "View on GitHub (pinned to 1b2de5e052)
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).
Example fix
// before
'variables': { 'my_list': ['a', {'nested': 1}, 'b'] },
'args': ['<(my_list)'],
// after — keep the list homogeneous in strings
'variables': { 'my_list': ['a', 'b', 'nested'] }, Defensive patterns
Strategy: type-guard
Validate before calling
def check_list_var(name, value):
if isinstance(value, list):
bad = [type(x).__name__ for x in value if type(x) not in (str, int)]
assert not bad, f'{name} list contains non-str/int: {bad}' Type guard
def is_str_int_list(value) -> bool:
return isinstance(value, list) and all(type(x) in (str, int) for x in value) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Variable expansion in this context permits strings and lists
- Variable %s must expand to a string or list of strings; foun
- Variable expansion in this context permits str and int only,
- Unknown type {item.__class__.__name__} at index {index}
- expected string list; got %r
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/701e908faf5ae3e8.
Report an issue: GitHub.