d2lang/d2 · error
can only delete one edge at a time
Error message
can only delete one edge at a time
What it means
d2oracle.Delete, like Set, handles only one edge per key. A key parsing to multiple edges (a chain such as `(a -> b -> c)`) is ambiguous for deletion, so Delete refuses it up front.
Source
Thrown at d2oracle/edit.go:947
}
if !m.IsFileMap() && m.Range.OneLine() {
// This doesn't require any shenanigans to prevent consuming sibling spacing because
// d2format will use the mapkey's range to determine whether to insert extra newlines.
// See TestCreate/make_scope_multiline_spacing_2
m.Range.End.Line++
}
}
func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, err error) {
defer xdefer.Errorf(&err, "failed to delete %#v", key)
mk, err := d2parser.ParseMapKey(key)
if err != nil {
return nil, err
}
if len(mk.Edges) > 1 {
return nil, errors.New("can only delete one edge at a time")
}
if len(mk.Edges) == 1 {
edgeTrimCommon(mk)
}
boardG := g
baseAST := g.AST
if len(boardPath) > 0 {
// When compiling a nested board, we can read from boardG but only write to baseBoardG
boardG = GetBoardGraph(g, boardPath)
if boardG == nil {
return nil, fmt.Errorf("board %v not found", boardPath)
}
// TODO beter name
baseAST = boardG.BaseAST
if baseAST == nil {View on GitHub (pinned to 0d69dca6f5)
Solutions
- Delete one edge at a time using an index: `(a -> b -> c)[0]`, then `[1]` (recomputing indices after each delete).
- Issue multiple Delete calls, one per edge segment.
- Pre-check len(mk.Edges) <= 1 by parsing the key with d2parser.ParseMapKey before calling Delete.
- If deleting a chain, delete its composing edges individually to keep indices valid.
Example fix
// before g, err = d2oracle.Delete(g, nil, "(a -> b -> c)") // after g, err = d2oracle.Delete(g, nil, "(a -> b -> c)[1]") g, err = d2oracle.Delete(g, nil, "(a -> b -> c)[0]")
Defensive patterns
Strategy: validation
Validate before calling
mk, err := d2parser.ParseMapKey(key)
if err != nil { return err }
if len(mk.Edges) > 1 {
return fmt.Errorf("split %q: Delete handles one edge per call, use [index]", key)
} Type guard
func isSingleEdgeKey(key string) bool {
mk, err := d2parser.ParseMapKey(key)
return err == nil && len(mk.Edges) <= 1
} Try / catch
_, err := d2oracle.Delete(g, nil, key)
if err != nil && err.Error() == "can only delete one edge at a time" {
return fmt.Errorf("delete edges of %q one indexed key at a time", key)
} Prevention
- Delete chains segment by segment with explicit [index] keys
- Recompute indices after each delete since they shift
- Validate keys with ParseMapKey before invoking Delete
- Do not assume Delete removes an entire chain
When it happens
Trigger: Calling d2oracle.Delete with a key containing two or more edges, e.g. `x.(a -> b -> c)` or `(a <-> b <-> c)`, without an index selecting a single edge.
Common situations: Deleting a whole chain expecting all segments to be removed; forwarding user-typed chain keys from an editor; programmatic key generation joining multiple connections into one key.
Related errors
- can only set one edge at a time
- edge key must be reserved
- edgeKey must be an edge
- edgeKey must refer to an existing edge
- edge not found
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/36ca431cd9747e79.
Report an issue: GitHub.