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

  1. 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 <().
  2. If you need a particular field, reference '<(foo.key)' or restructure so the expandable value is a scalar.
  3. 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

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


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/72513dfecfd5350b. Report an issue: GitHub.