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

  1. Move the edge's endpoints (nodes) instead of the edge itself; the edge stays defined in its original scope.
  2. Recreate the edge in the target scope: Delete the old edge and Create a new one with the desired key/endpoints.
  3. Keep the edge's map key identical across the move (same formatted Key) so the move is within-scope.
  4. 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

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


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