d2lang/d2 · error
cannot rename edge to edge chain
Error message
cannot rename edge to edge chain
What it means
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.
Source
Thrown at d2oracle/edit.go:2885
conflictOldIDs[ch] = ch.ID
conflictNewIDs[ch] = newAK[len(newAK)-1]
newIDs = append(newIDs, d2format.Format(newMK.Key))
} else {
newIDs = append(newIDs, d2format.Format(hoistedMK.Key))
}
}
}
}
if len(mk.Edges) > 1 {
return nil, nil
}
if len(mk.Edges) == 1 {
if len(mk.Edges) == 0 {
return nil, errors.New("cannot rename edge to node")
}
if len(mk.Edges) > 1 {
return nil, errors.New("cannot rename edge to edge chain")
}
e, ok := obj.HasEdge(mk)
if !ok {
return nil, nil
}
beforeID := e.AbsID()
tmp := *e
e2 := &tmp
e2.SrcArrow = mk2.Edges[0].SrcArrow == "<"
e2.DstArrow = mk2.Edges[0].DstArrow == ">"
deltas[beforeID] = e2.AbsID()
return deltas, nil
}
beforeObjID := obj.ID
toParent := g.RootView on GitHub (pinned to 0d69dca6f5)
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
Example fix
// before
mk2, _ := d2parser.ParseMapKey(newName) // newName: "a -> b -> c"
renamed, err := oracle.MoveIDDeltas(ctx, g, "a -> b", newName, nil) // error
// after
mk2, _ := d2parser.ParseMapKey(newName)
if len(mk2.Edges) == 1 {
renamed, err = oracle.MoveIDDeltas(ctx, g, "a -> b", newName, nil)
} else {
// delete old edge, add each chain edge separately
} Defensive patterns
Strategy: validation
Validate before calling
mk2, err := d2parser.ParseMapKey(newName)
if err != nil { return err }
if len(mk2.Edges) > 1 { return errors.New("destination must be a single edge, not a chain") } Type guard
func isNotEdgeChain(newName string) bool {
mk, err := d2parser.ParseMapKey(newName)
return err == nil && mk != nil && len(mk.Edges) <= 1
} Try / catch
_, err := oracle.MoveIDDeltas(ctx, g, oldID, newName, nil)
if err != nil && strings.Contains(err.Error(), "edge chain") {
// split into delete + add-chain operations
} Prevention
- 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
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- cannot rename edge to node
- edgeKey must be an edge
- edgeKey must refer to an existing edge
- edge not found
- newSrc not found
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/f8595a3ef87fad64.
Report an issue: GitHub.