d2lang/d2 · error

key does not exist

Error message

key does not exist

What it means

Rename checks that the key being renamed actually exists as a child of the board's root via HasChild; if not found, it returns "key does not exist". The rename is aborted because there is nothing to rename.

Source

Thrown at d2oracle/edit.go:1736

	if len(mk.Edges) > 0 && mk.EdgeKey == nil {
		// TODO: Not a fan of this dual interpretation depending on mk.Edges.
		// Maybe we remove Rename and just have Move.
		mk2, err := d2parser.ParseMapKey(newName)
		if err != nil {
			return nil, "", err
		}

		mk2.Key = mk.Key
		mk = mk2
	} else {
		_, ok := d2ast.ReservedKeywords[newName]
		if ok {
			return nil, "", fmt.Errorf("cannot rename to reserved keyword: %#v", newName)
		}
		if mk.Key != nil {
			obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key))
			if !ok {
				return nil, "", fmt.Errorf("key does not exist")
			}
			// If attempt to name something "x", but "x" already exists, rename it "x 2" instead
			generatedName, _, err := generateUniqueKey(boardG, newName, obj, nil)
			if err == nil {
				newName = generatedName
			}
		}
		// TODO: Handle mk.EdgeKey
		mk.Key.Path[len(mk.Key.Path)-1] = d2ast.MakeValueBox(d2ast.RawString(newName, true)).StringBox()
	}

	g, err = move(g, boardPath, key, d2format.Format(mk), false)
	return g, newName, err
}

func trimReservedSuffix(path []*d2ast.StringBox) []*d2ast.StringBox {
	for i, p := range path {
		if _, ok := d2ast.ReservedKeywords[p.Unbox().ScalarString()]; ok {

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Check g.Root.HasChild(d2graph.Key(parsedKey)) or equivalent before renaming.
  2. Correct the key string; remember d2 keys are case-sensitive.
  3. Recompile the graph so it reflects the current object set.
  4. Target the board where the object is actually defined via boardPath.

Example fix

// before
d2oracle.Rename(g, nil, "server.old", "server.new") // server.old gone
// after
if _, ok := g.Root.HasChild([]string{"server","old"}); ok {
    d2oracle.Rename(g, nil, "server.old", "server.new")
}
Defensive patterns

Strategy: validation

Validate before calling

import ("oss.terrastruct.com/d2/d2graph"; "oss.terrastruct.com/d2/d2parser")
mk, _ := d2parser.ParseMapKey(key)
if _, ok := g.Root.HasChild(d2graph.Key(mk)); !ok {
    return fmt.Errorf("cannot rename: %q does not exist", key)
}

Type guard

func keyExists(g *d2graph.Graph, key string) bool {
    mk, err := d2parser.ParseMapKey(key)
    if err != nil { return false }
    _, ok := g.Root.HasChild(d2graph.Key(mk))
    return ok
}

Try / catch

ng, _, err := d2oracle.Rename(g, boardPath, key, newName)
if err != nil && strings.Contains(err.Error(), "key does not exist") {
    return fmt.Errorf("object %q no longer exists; refresh diagram", key)
}

Prevention

When it happens

Trigger: d2oracle.Rename(g, boardPath, key, newName) where key (parsed via d2parser) does not match an existing object path on the target board: typo, object already deleted, or key living on a different board than boardPath.

Common situations: Stale editor state where the object was removed by a previous operation; renaming an object defined via glob/import not addressable by this literal key; case mismatches in key.

Related errors


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