nodejs/node · error · GypError

Key '%s' repeated at level %s with key path '%s'

Error message

Key '%s' repeated at level %s with key path '%s'

What it means

Raised as a GypError by CheckNode during 'checked' evaluation of a .gyp file when a dictionary literal contains the same key twice. Gyp performs AST-level duplicate-key detection because Python's native dict construction would silently drop the earlier value, hiding a real mistake.

Source

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

    """

    syntax_tree = ast.parse(file_contents)
    assert isinstance(syntax_tree, ast.Module)
    c1 = syntax_tree.body
    assert len(c1) == 1
    c2 = c1[0]
    assert isinstance(c2, ast.Expr)
    return CheckNode(c2.value, [])


def CheckNode(node, keypath):
    if isinstance(node, ast.Dict):
        dict = {}
        for key, value in zip(node.keys, node.values):
            assert isinstance(key, ast.Str)
            key = key.s
            if key in dict:
                raise GypError(
                    "Key '"
                    + key
                    + "' repeated at level "
                    + repr(len(keypath) + 1)
                    + " with key path '"
                    + ".".join(keypath)
                    + "'"
                )
            kp = list(keypath)  # Make a copy of the list for descending this node.
            kp.append(key)
            dict[key] = CheckNode(value, kp)
        return dict
    elif isinstance(node, ast.List):
        children = []
        for index, child in enumerate(node.elts):
            kp = list(keypath)  # Copy list.
            kp.append(repr(index))
            children.append(CheckNode(child, kp))

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Find the duplicate key reported (the key, nesting level, and key path are in the message) and remove or rename one occurrence.
  2. If two values must coexist, merge them into a single key (e.g. combine two 'sources' lists).
  3. Use the key path from the message to navigate directly to the offending dict.

Example fix

// before
'targets': [{
  'target_name': 'foo',
  'sources': ['a.cc'],
  'sources': ['b.cc']
}]
// after
'targets': [{
  'target_name': 'foo',
  'sources': ['a.cc', 'b.cc']
}]
Defensive patterns

Strategy: validation

Validate before calling

import ast
for fn in gyp_files:
    tree = ast.parse(open(fn).read())
    for node in ast.walk(tree):
        if isinstance(node, ast.Dict):
            keys = [k.s for k in node.keys if isinstance(k, ast.Str)]
            if len(keys) != len(set(keys)):
                raise ValueError(f'duplicate keys in {fn}')

Type guard

import ast
def has_no_dup_keys(node) -> bool:
    if isinstance(node, ast.Dict):
        keys = [k.s for k in node.keys if isinstance(k, ast.Str)]
        if len(keys) != len(set(keys)):
            return False
    return all(has_no_dup_keys(c) for c in ast.iter_child_nodes(node))

Prevention

When it happens

Trigger: A .gyp/.gypi file evaluated in check mode contains a dict with a repeated key (e.g. two 'sources' entries in the same target). CheckNode tracks seen keys per dict and raises at input.py:189 when a repeat is found.

Common situations: Copy-pasting a block and forgetting to remove the original; merging two config snippets that both define the same key; a conditional that, combined with the base, produces a duplicate (though conditions are merged separately, a literal duplicate in one dict is caught).

Related errors


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