d2lang/d2 · error
moving across scopes isn't supported for edges
Error message
moving across scopes isn't supported for edges
What it means
This error is thrown by d2oracle's move function when asked to move an edge (a connection like 'a -> b') to a different scope. Edge keys encode both endpoints and scope, so relocating an edge to a differently-scoped map key (where the formatted key of the move differs from the target key) is not a supported edit. The library intentionally rejects the operation instead of producing a corrupt rewrite.
Source
Thrown at d2oracle/edit.go:1808
return nil, err
}
mk, err := d2parser.ParseMapKey(key)
if err != nil {
return nil, err
}
mk2, err := d2parser.ParseMapKey(newKey)
if err != nil {
return nil, err
}
edgeTrimCommon(mk)
edgeTrimCommon(mk2)
if len(mk.Edges) > 0 && mk.EdgeKey == nil {
if d2format.Format(mk.Key) != d2format.Format(mk2.Key) {
// TODO just prevent moving edges at all
return nil, errors.New("moving across scopes isn't supported for edges")
}
obj := g.Root
if mk.Key != nil {
var ok bool
obj, ok = g.Root.HasChild(d2graph.Key(mk.Key))
if !ok {
return nil, fmt.Errorf("edge referenced by from does not exist")
}
}
e, ok := obj.HasEdge(mk)
if !ok {
return nil, fmt.Errorf("edge referenced by to does not exist")
}
_, ok = obj.HasEdge(mk2)
if ok {
return nil, fmt.Errorf("to edge already exists")
}
View on GitHub (pinned to 0d69dca6f5)
Solutions
- Move the edge's endpoints (nodes) instead of the edge itself; the edge stays defined in its original scope.
- Recreate the edge in the target scope: Delete the old edge and Create a new one with the desired key/endpoints.
- Keep the edge's map key identical across the move (same formatted Key) so the move is within-scope.
- Patch your tooling to detect edges in mk.Edges before calling Move and route them to a delete+create flow.
Example fix
// before delta, err := d2oracle.Move(graph.Id, "x.y -> x.z", "x.a.y -> x.a.z") // cross-scope edge move: error // after // move the endpoints, not the edge: delta, err := d2oracle.Move(graph.Id, "x.y", "x.a.y") delta2, err2 := d2oracle.Move(graph.Id, "x.z", "x.a.z")
Defensive patterns
Strategy: validation
Validate before calling
mk, err := d2parser.ParseMapKey(fromKey)
if err != nil { return err }
mk2, err := d2parser.ParseMapKey(toKey)
if err != nil { return err }
if len(mk.Edges) > 0 && mk.EdgeKey == nil && d2format.Format(mk.Key) != d2format.Format(mk2.Key) {
return fmt.Errorf("edge %q cannot be moved across scopes; delete+create instead", fromKey)
} Type guard
func isEdgeWithinScope(mk, mk2 *d2parser.MapKey) bool {
return len(mk.Edges) == 0 || mk.EdgeKey != nil || d2format.Format(mk.Key) == d2format.Format(mk2.Key)
} Try / catch
delta, err := d2oracle.Move(id, fromKey, toKey)
if err != nil {
if err.Error() == "moving across scopes isn't supported for edges" {
return deleteAndRecreateEdge(g, fromKey, toKey)
}
return err
} Prevention
- Check len(mk.Edges) before calling Move/Rename on a key
- Model edge relocation as delete+create in tooling
- Keep edges in the scope where their endpoints are defined
- Route node moves and edge edits through separate code paths
When it happens
Trigger: Calling d2oracle.Move, d2oracle.Rename, or renameConflictsToParent with mk.Edges non-empty and mk.EdgeKey == nil, where d2format.Format(mk.Key) differs from d2format.Format(mk2.Key) — i.e. the edge's map key path changes across scopes.
Common situations: Programmatic graph refactors that try to drag an edge into or out of a nested container/shape; renaming a parent shape while moving its edge to another scope in the same edit; tools built on d2oracle that assume edges move like nodes.
Related errors
- not found
- cannot generate unique key for edge chain
- edgeKey must be an edge
- edgeKey must refer to an existing edge
- dimensions for edge label %#v not found
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/523921d50d196da2.
Report an issue: GitHub.