nodejs/node · error · GypError
extension %s associated with multiple rules, target %s rules
Error message
extension %s associated with multiple rules, target %s rules %s and %s
What it means
ValidateRulesInTarget also enforces that each file extension maps to at most one rule per target. After stripping an optional leading dot it records the extension; a second rule claiming the same extension is rejected because GYP would not know which rule to invoke on matching source files.
Source
Thrown at tools/gyp/pylib/gyp/input.py:2733
# Dicts to map between values found in rules' 'rule_name' and 'extension'
# keys and the rule dicts themselves.
rule_names = {}
rule_extensions = {}
rules = target_dict.get("rules", [])
for rule in rules:
# Make sure that there's no conflict among rule names and extensions.
rule_name = rule["rule_name"]
if rule_name in rule_names:
raise GypError(f"rule {rule_name} exists in duplicate, target {target}")
rule_names[rule_name] = rule
rule_extension = rule["extension"]
if rule_extension.startswith("."):
rule_extension = rule_extension[1:]
if rule_extension in rule_extensions:
raise GypError(
(
"extension %s associated with multiple rules, "
+ "target %s rules %s and %s"
)
% (
rule_extension,
target,
rule_extensions[rule_extension]["rule_name"],
rule_name,
)
)
rule_extensions[rule_extension] = rule
# Make sure rule_sources isn't already there. It's going to be
# created below if needed.
if "rule_sources" in rule:
raise GypError(
"rule_sources must not exist in input, target %s rule %s"View on GitHub (pinned to 1b2de5e052)
Solutions
- Find the two rules whose extension matches (remember the leading dot is optional on both sides).
- Decide which single rule should own that extension for this target, or split the source types across separate targets.
- Delete or repoint the losing rule's extension to a distinct value, then re-run gyp.
Example fix
// before
{ 'rule_name': 'rule_a', 'extension': 'foo', ... },
{ 'rule_name': 'rule_b', 'extension': '.foo', ... },
// after
{ 'rule_name': 'rule_a', 'extension': 'foo', ... },
{ 'rule_name': 'rule_b', 'extension': 'bar', ... }, Defensive patterns
Strategy: validation
Validate before calling
# Check one extension per rule per target, ignoring leading dots.
exts = set()
for r in target_dict.get('rules', []):
ext = r.get('extension', '').lstrip('.')
if ext in exts:
raise ValueError(f'extension {ext!r} claimed by multiple rules')
exts.add(ext) Type guard
def has_unique_rule_extensions(target_dict: dict) -> bool:
exts = [r.get('extension', '').lstrip('.') for r in target_dict.get('rules', [])]
return len(exts) == len(set(exts)) Prevention
- Treat file extensions as owned resources: one rule per extension per target.
- When splitting codegen across rules, give each a distinct extension.
- Document which extensions each rule consumes.
When it happens
Trigger: Two entries in one target's 'rules' list whose 'extension' keys (after a leading '.' is removed) compare equal, e.g. 'foo' and '.foo'.
Common situations: Adding a second code-generation rule for the same file type; merging gypi files that each define a rule for the same extension; changing a rule's extension to one already covered by another rule in the same target.
Related errors
- rule {rule_name} exists in duplicate, target {target}
- rule_sources must not exist in input, target %s rule %s
- Duplicate targets in isolate map files: %s.
- AddFileConfig: file "%s" not in project.
- expected string; got %r
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/be2b9681bb7ab6f2.
Report an issue: GitHub.