{"record":{"id":"01959cd2f98a0e35","repo":"d2lang/d2","slug":"invalid-underscore","errorCode":null,"errorMessage":"invalid underscore","messagePattern":"invalid underscore","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2ir/d2ir.go","lineNumber":746,"sourceCode":"func (eid *EdgeID) resolve(m *Map) (_ *EdgeID, _ *Map, common []d2ast.String, _ error) {\n\teid = eid.Copy()\n\tmaxUnderscores := go2.Max(countUnderscores(eid.SrcPath), countUnderscores(eid.DstPath))\n\tfor i := 0; i < maxUnderscores; i++ {\n\t\tif eid.SrcPath[0].ScalarString() == \"_\" && eid.SrcPath[0].IsUnquoted() {\n\t\t\teid.SrcPath = eid.SrcPath[1:]\n\t\t} else {\n\t\t\tmf := ParentField(m)\n\t\t\teid.SrcPath = append([]d2ast.String{mf.Name}, eid.SrcPath...)\n\t\t}\n\t\tif eid.DstPath[0].ScalarString() == \"_\" && eid.DstPath[0].IsUnquoted() {\n\t\t\teid.DstPath = eid.DstPath[1:]\n\t\t} else {\n\t\t\tmf := ParentField(m)\n\t\t\teid.DstPath = append([]d2ast.String{mf.Name}, eid.DstPath...)\n\t\t}\n\t\tm = ParentMap(m)\n\t\tif m == nil {\n\t\t\treturn nil, nil, nil, errors.New(\"invalid underscore\")\n\t\t}\n\t}\n\n\tfor len(eid.SrcPath) > 1 && len(eid.DstPath) > 1 {\n\t\tif !strings.EqualFold(eid.SrcPath[0].ScalarString(), eid.DstPath[0].ScalarString()) || strings.Contains(eid.SrcPath[0].ScalarString(), \"*\") {\n\t\t\treturn eid, m, common, nil\n\t\t}\n\t\tcommon = append(common, eid.SrcPath[0])\n\t\teid.SrcPath = eid.SrcPath[1:]\n\t\teid.DstPath = eid.DstPath[1:]\n\t}\n\n\treturn eid, m, common, nil\n}\n\ntype Edge struct {\n\t// *Map\n\tparent    Node","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2ir/d2ir.go#L728-L764","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (edge in a scope with no resolvable parent)\n_.x -> y\n// after\nroot.x -> y","handlingStrategy":"validation","validationCode":"// Ensure underscore edges are declared in a nested scope that has a parent field chain\nfunc edgeScopeResolvable(scope *d2ir.Map, edgeKey string) bool {\n  return !strings.Contains(edgeKey, \"_\") || scope != nil && ParentField(scope) != nil || ParentMap(scope) != nil\n}","typeGuard":"func hasParentScope(m *d2ir.Map) bool { return ParentMap(m) != nil }","tryCatchPattern":"eid, _, _, err := d2ir.EidID(...) // or via compile\nif err != nil && err.Error() == \"invalid underscore\" {\n  // replace _-prefixed endpoints with absolute names and recompile\n}","preventionTips":["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."],"tags":["d2","ir","edge-resolution","underscore"],"backgroundTag":"invalid-parent-reference","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}