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.Root

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Keep the renamed ID a single edge: ensure newName parses to exactly one edge before calling MoveIDDeltas
  2. If a chain is required, delete the original edge and create the chain edges as new edges via the oracle instead of renaming
  3. 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

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


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/f8595a3ef87fad64. Report an issue: GitHub.