{"record":{"id":"762dfb0570a54092","repo":"d2lang/d2","slug":"cannot-rename-edge-to-node","errorCode":null,"errorMessage":"cannot rename edge to node","messagePattern":"cannot rename edge to node","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2oracle/edit.go","lineNumber":2882,"sourceCode":"\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tnewAK := d2graph.Key(newMK.Key)\n\t\t\t\t\tconflictOldIDs[ch] = ch.ID\n\t\t\t\t\tconflictNewIDs[ch] = newAK[len(newAK)-1]\n\t\t\t\t\tnewIDs = append(newIDs, d2format.Format(newMK.Key))\n\t\t\t\t} else {\n\t\t\t\t\tnewIDs = append(newIDs, d2format.Format(hoistedMK.Key))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(mk.Edges) > 1 {\n\t\treturn nil, nil\n\t}\n\tif len(mk.Edges) == 1 {\n\t\tif len(mk.Edges) == 0 {\n\t\t\treturn nil, errors.New(\"cannot rename edge to node\")\n\t\t}\n\t\tif len(mk.Edges) > 1 {\n\t\t\treturn nil, errors.New(\"cannot rename edge to edge chain\")\n\t\t}\n\n\t\te, ok := obj.HasEdge(mk)\n\t\tif !ok {\n\t\t\treturn nil, nil\n\t\t}\n\t\tbeforeID := e.AbsID()\n\t\ttmp := *e\n\t\te2 := &tmp\n\t\te2.SrcArrow = mk2.Edges[0].SrcArrow == \"<\"\n\t\te2.DstArrow = mk2.Edges[0].DstArrow == \">\"\n\t\tdeltas[beforeID] = e2.AbsID()\n\t\treturn deltas, nil\n\t}\n","sourceCodeStart":2864,"sourceCodeEnd":2900,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2oracle/edit.go#L2864-L2900","documentation":"MoveIDDeltas in d2oracle validates a rename target of an existing edge. When the renamed ID parses to exactly one edge, it tries to move that edge; the check for a zero-edge target is logically dead inside the len==1 branch, but the message communicates that renaming an edge to a plain node ID is not a supported move operation. The library refuses edge-to-node renames because the move logic only knows how to relocate an edge, not convert it.","triggerScenarios":"Calling MoveIDDeltas (directly or via updateNear) where the old ID is an edge (e.g. 'a -> b') and the parsed mk has exactly one edge, hitting the (unreachable-by-construction, defensive) len==0 guard inside the single-edge branch.","commonSituations":"Renaming an edge ID in a D2 script via the oracle API and accidentally supplying a node name instead of an edge expression; renaming between boards where the new name loses its edge arrow; tooling that reuses MoveIDDeltas for generic ID renames without checking edge-ness of the new name.","solutions":["Use the correct API for node renames: call MoveIDDeltas only when newName also parses to an edge with exactly one edge","Parse newName with d2parser.ParseMapKey before calling and check len(mk.Edges)==1 to confirm it is still an edge","Convert the edge into a node manually instead: delete the edge and create the node via separate oracle edit operations"],"exampleFix":"// before\nrenamed, err := oracle.MoveIDDeltas(ctx, g, \"x -> y\", \"myNode\", nil) // old id is edge, new is node\n// after\nmk2, _ := d2parser.ParseMapKey(newName)\nif len(mk.Edges) == 1 {\n    renamed, err = oracle.MoveIDDeltas(ctx, g, \"x -> y\", newName, nil)\n} else {\n    // handle node rename via a different path\n}","handlingStrategy":"validation","validationCode":"mk, err := d2parser.ParseMapKey(newName)\nif err != nil { return err }\nif len(mk.Edges) != 1 { return errors.New(\"rename of an edge requires a single-edge new name\") }","typeGuard":"func isSingleEdge(newName string) bool {\n    mk, err := d2parser.ParseMapKey(newName)\n    return err == nil && mk != nil && len(mk.Edges) == 1\n}","tryCatchPattern":"renamed, err := oracle.MoveIDDeltas(ctx, g, oldID, newName, nil)\nif err != nil && strings.Contains(err.Error(), \"cannot rename edge\") {\n    // fall back to delete-edge + add-node flow\n}","preventionTips":["Parse the new name and count mk.Edges before any edge rename","Never pass plain node names as newName when the old ID is an edge","Centralize edge renames behind a helper that validates edge-ness"],"tags":["d2","rename","edge"],"backgroundTag":"edge-to-node-rename","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}