{"record":{"id":"f8595a3ef87fad64","repo":"d2lang/d2","slug":"cannot-rename-edge-to-edge-chain","errorCode":null,"errorMessage":"cannot rename edge to edge chain","messagePattern":"cannot rename edge to edge chain","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2oracle/edit.go","lineNumber":2885,"sourceCode":"\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\n\tbeforeObjID := obj.ID\n\n\ttoParent := g.Root","sourceCodeStart":2867,"sourceCodeEnd":2903,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2oracle/edit.go#L2867-L2903","documentation":"MoveIDDeltas rejects renaming a single edge to a multi-hop edge chain (e.g. 'a -> b -> c'). Moving logic only supports a one-to-one edge rename; an edge chain in the new name is a different graph structure the move routine cannot express, so it errors out.","triggerScenarios":"Calling MoveIDDeltas with an old ID that parses to exactly one edge (len(mk.Edges)==1) and a newName that parses to more than one edge (len(mk2.Edges)>1), e.g. renaming 'a -> b' to 'a -> b -> c'.","commonSituations":"Users editing D2 text renaming an edge into a chained edge; IDE/refactor tooling that rewrites IDs without validating the arrow count; renaming inside board scopes where the new name acquires extra arrows.","solutions":["Keep the renamed ID a single edge: ensure newName parses to exactly one edge before calling MoveIDDeltas","If a chain is required, delete the original edge and create the chain edges as new edges via the oracle instead of renaming","Split the chain creation into separate Set/Add operations for each hop"],"exampleFix":"// before\nmk2, _ := d2parser.ParseMapKey(newName) // newName: \"a -> b -> c\"\nrenamed, err := oracle.MoveIDDeltas(ctx, g, \"a -> b\", newName, nil) // error\n// after\nmk2, _ := d2parser.ParseMapKey(newName)\nif len(mk2.Edges) == 1 {\n    renamed, err = oracle.MoveIDDeltas(ctx, g, \"a -> b\", newName, nil)\n} else {\n    // delete old edge, add each chain edge separately\n}","handlingStrategy":"validation","validationCode":"mk2, err := d2parser.ParseMapKey(newName)\nif err != nil { return err }\nif len(mk2.Edges) > 1 { return errors.New(\"destination must be a single edge, not a chain\") }","typeGuard":"func isNotEdgeChain(newName string) bool {\n    mk, err := d2parser.ParseMapKey(newName)\n    return err == nil && mk != nil && len(mk.Edges) <= 1\n}","tryCatchPattern":"_, err := oracle.MoveIDDeltas(ctx, g, oldID, newName, nil)\nif err != nil && strings.Contains(err.Error(), \"edge chain\") {\n    // split into delete + add-chain operations\n}","preventionTips":["Reject destination IDs with more than one arrow before moving","Use delete + add for structural changes like chaining edges","Add unit tests covering edge-chain rename attempts"],"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"}