d2lang/d2 · error
invalid underscore
Error message
invalid underscore
What it means
During edge id resolution in d2ir, when an underscore reference (`_`) is expanded the algorithm walks up to the parent map to resolve the edge endpoint. If the parent map is nil (no further scope to climb), the underscore reference is unresolvable and this error is thrown.
Source
Thrown at d2ir/d2ir.go:746
func (eid *EdgeID) resolve(m *Map) (_ *EdgeID, _ *Map, common []d2ast.String, _ error) {
eid = eid.Copy()
maxUnderscores := go2.Max(countUnderscores(eid.SrcPath), countUnderscores(eid.DstPath))
for i := 0; i < maxUnderscores; i++ {
if eid.SrcPath[0].ScalarString() == "_" && eid.SrcPath[0].IsUnquoted() {
eid.SrcPath = eid.SrcPath[1:]
} else {
mf := ParentField(m)
eid.SrcPath = append([]d2ast.String{mf.Name}, eid.SrcPath...)
}
if eid.DstPath[0].ScalarString() == "_" && eid.DstPath[0].IsUnquoted() {
eid.DstPath = eid.DstPath[1:]
} else {
mf := ParentField(m)
eid.DstPath = append([]d2ast.String{mf.Name}, eid.DstPath...)
}
m = ParentMap(m)
if m == nil {
return nil, nil, nil, errors.New("invalid underscore")
}
}
for len(eid.SrcPath) > 1 && len(eid.DstPath) > 1 {
if !strings.EqualFold(eid.SrcPath[0].ScalarString(), eid.DstPath[0].ScalarString()) || strings.Contains(eid.SrcPath[0].ScalarString(), "*") {
return eid, m, common, nil
}
common = append(common, eid.SrcPath[0])
eid.SrcPath = eid.SrcPath[1:]
eid.DstPath = eid.DstPath[1:]
}
return eid, m, common, nil
}
type Edge struct {
// *Map
parent NodeView on GitHub (pinned to 0d69dca6f5)
Solutions
- Fix the edge path so the `_` reference has a valid parent scope; use absolute names if unsure.
- Ensure the edge is declared inside the nested scope that the `_` refers to, not at a boundary where no parent exists.
- Replace `_`-prefixed endpoints with explicit full paths.
Example fix
// before (edge in a scope with no resolvable parent) _.x -> y // after root.x -> y
Defensive patterns
Strategy: validation
Validate before calling
// Ensure underscore edges are declared in a nested scope that has a parent field chain
func edgeScopeResolvable(scope *d2ir.Map, edgeKey string) bool {
return !strings.Contains(edgeKey, "_") || scope != nil && ParentField(scope) != nil || ParentMap(scope) != nil
} Type guard
func hasParentScope(m *d2ir.Map) bool { return ParentMap(m) != nil } Try / catch
eid, _, _, err := d2ir.EidID(...) // or via compile
if err != nil && err.Error() == "invalid underscore" {
// replace _-prefixed endpoints with absolute names and recompile
} Prevention
- Use `_` edges only inside nested scopes with valid parents.
- Prefer absolute endpoints in generated edge keys.
- Re-check edge keys after moving declarations between scopes.
When it happens
Trigger: An edge key containing `_` whose endpoint cannot be resolved because ParentMap(m) is nil — e.g. an edge declared in a scope where the `_` prefix climbs above the root or in a context (like inside a connection's own field) with no parent field chain to resolve against.
Common situations: Malformed relative edges like `(a-b)._x` or underscore edges at unexpected nesting; refactoring that moved an edge with `_.` prefixes into a shallower scope; typos in connection keys using `_` incorrectly.
Related errors
- expected "shadow" to be true or false
- expected "3d" to be true or false
- expected "multiple" to be true or false
- expected "font-size" to be a number between 8 and 100
- expected "font-color" to be a valid named color ("orange"),
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/01959cd2f98a0e35.
Report an issue: GitHub.