nodejs/node · error · GypError

%s must be a list

Error message

%s must be a list

What it means

GYP's 'conditions' and 'target_conditions' sections are lists of [cond_expr, true_dict, (false_dict)] triples. EvalCondition first asserts the whole entry is a Python list; if a single entry is a dict, string, or any non-list type, it raises this GypError. The conditions_key in the message tells you whether the failure is in a 'conditions' or 'target_conditions' block.

Source

Thrown at tools/gyp/pylib/gyp/input.py:1116

        for index, outstr in enumerate(output):
            if IsStrCanonicalInt(outstr):
                output[index] = int(outstr)
    elif IsStrCanonicalInt(output):
        output = int(output)

    return output


# The same condition is often evaluated over and over again so it
# makes sense to cache as much as possible between evaluations.
cached_conditions_asts = {}


def EvalCondition(condition, conditions_key, phase, variables, build_file):
    """Returns the dict that should be used or None if the result was
    that nothing should be used."""
    if not isinstance(condition, list):
        raise GypError(conditions_key + " must be a list")
    if len(condition) < 2:
        # It's possible that condition[0] won't work in which case this
        # attempt will raise its own IndexError.  That's probably fine.
        raise GypError(
            conditions_key
            + " "
            + condition[0]
            + " must be at least length 2, not "
            + str(len(condition))
        )

    i = 0
    result = None
    while i < len(condition):
        cond_expr = condition[i]
        true_dict = condition[i + 1]
        if not isinstance(true_dict, dict):
            raise GypError(

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Wrap each condition as a list: [cond_expr_string, true_dict] (plus optional false_dict), and ensure the whole 'conditions' value is a list of such lists.
  2. Validate the .gyp file parses as expected with python -c "import json; json.load(open('file.gyp'))" (GYP uses a JSON-ish parser).
  3. Check the conditions_key in the message to find the offending block (conditions vs target_conditions).

Example fix

// before
'conditions': {
  'OS == "linux"': { 'defines': ['LINUX'] }
},
// after
'conditions': [
  ['OS == "linux"', { 'defines': ['LINUX'] }]
],
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_conditions(conditions, key='conditions'):
    assert isinstance(conditions, list), f'{key} must be a list of [expr, dict, ...] entries'
    for entry in conditions:
        assert isinstance(entry, list), f'{key} entry {entry!r} must be a list'

Type guard

def is_conditions_well_formed(conditions) -> bool:
    return isinstance(conditions, list) and all(isinstance(e, list) for e in conditions)

Prevention

When it happens

Trigger: Writing "'conditions': { 'os==\"mac\"': {...} }" (a dict) instead of "'conditions': [ ['os==\"mac\"', {...}] ]" (a list of lists); pasting a YAML-style mapping into a JSON-style .gyp file; a programmatic generator emitting conditions as objects.

Common situations: Confusing the conditions-list syntax with a mapping; missing the outer '[' ']'; malformed hand-edits to a .gyp file; converting from another build system's conditional syntax.

Related errors


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