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
- Find the duplicate key reported (the key, nesting level, and key path are in the message) and remove or rename one occurrence.
- If two values must coexist, merge them into a single key (e.g. combine two 'sources' lists).
- 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
- Lint .gyp files for duplicate dict keys.
- Avoid copy-paste that duplicates keys; merge lists instead.
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
- %s does not evaluate to a dictionary.
- %s must not contain included_files key
- Unable to find targets in build file %s
- Unable to open file %s
- Unknown AST node at key path '%s': %s
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/57c3d49976db5234.
Report an issue: GitHub.