d2lang/d2 · error

cannot rename to reserved keyword: %#v

Error message

cannot rename to reserved keyword: %#v

What it means

Rename rejects newName values that collide with d2 reserved keywords (like shape, style, label, etc.) because those identifiers cannot be used as regular object keys; the library returns "cannot rename to reserved keyword" instead of producing a broken diagram.

Source

Thrown at d2oracle/edit.go:1731

		if boardG == nil {
			return nil, "", fmt.Errorf("board %v not found", boardPath)
		}
	}

	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

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Validate newName against d2ast.ReservedKeywords before calling Rename.
  2. Sanitize/escape user-supplied names (prefix, quote, or reject reserved words in UI).
  3. Pick a different name for the object.
  4. If a reserved name is genuinely needed, wrap it appropriately in the source rather than via Rename.

Example fix

// before
d2oracle.Rename(g, nil, "db", "style") // rejected
// after
if _, reserved := d2ast.ReservedKeywords[newName]; reserved {
    newName = newName + "-node"
}
d2oracle.Rename(g, nil, "db", newName)
Defensive patterns

Strategy: validation

Validate before calling

import "oss.terrastruct.com/d2/lib/d2ast"
if _, reserved := d2ast.ReservedKeywords[newName]; reserved {
    return fmt.Errorf("%q is a reserved d2 keyword; pick another name", newName)
}

Type guard

func isAllowedName(name string) bool {
    _, reserved := d2ast.ReservedKeywords[name]
    return name != "" && !reserved
}

Try / catch

ng, _, err := d2oracle.Rename(g, boardPath, key, newName)
if err != nil && strings.Contains(err.Error(), "reserved keyword") {
    return suggestAlternativeName(newName)
}

Prevention

When it happens

Trigger: Calling Rename with newName equal to any key in d2ast.ReservedKeywords, e.g. renaming an object to "shape" or "style" when it has no edge interpretation.

Common situations: Renaming based on raw user input in editors; scripted refactors that don't sanitize names; autocompletion suggesting reserved words.

Related errors


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