d2lang/d2 · error

board %v AST not found

Error message

board %v AST not found

What it means

After Create mutates the board's BaseAST, it splices the edited AST back into the root file AST with ReplaceBoardNode(g.AST, baseAST, boardPath). If no node at boardPath in the root AST matches baseAST, the splice fails and Create returns 'board %v AST not found', meaning the in-memory board and the root AST are out of sync.

Source

Thrown at d2oracle/edit.go:64

			return nil, "", fmt.Errorf("board %v cannot be modified through this file", boardPath)
		}
	}

	newKey, edge, err := generateUniqueKey(boardG, key, nil, nil)
	if err != nil {
		return nil, "", err
	}

	if edge {
		err = _set(boardG, baseAST, key, nil, nil)
	} else {
		err = _set(boardG, baseAST, newKey, nil, nil)
	}

	if len(boardPath) > 0 {
		replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
		if !replaced {
			return nil, "", fmt.Errorf("board %v AST not found", boardPath)
		}
	}

	if err != nil {
		return nil, "", err
	}
	g, err = recompile(g)
	if err != nil {
		return nil, "", err
	}
	return g, newKey, nil
}

// TODO: update graph in place when compiler can accept single modifications
// TODO: go through all references to decide best spot to insert something
func Set(g *d2graph.Graph, boardPath []string, key string, tag, value *string) (_ *d2graph.Graph, err error) {
	var valueHelp string
	if value == nil {

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Verify boardPath exactly matches the board names in the file (each element is a layer/scenario/step name as written in source).
  2. Recompile g from the current file text so g.AST is in sync before calling Create.
  3. Do not mutate or replace g.AST between obtaining the graph and calling Create.
  4. Edit the root board (empty boardPath) if the board structure is ambiguous.

Example fix

// before
g2, _, err := d2oracle.Create(g, []string{"Layers", "prod"}, "x") // wrong casing -> AST not found
// after
g2, _, err := d2oracle.Create(g, []string{"layers", "prod"}, "x") // must match source names
Defensive patterns

Strategy: validation

Validate before calling

func boardInAST(g *d2graph.Graph, boardPath []string) bool {
    // confirm each name appears under layers/scenarios/steps in the source text
    return len(boardPath) == 0 || d2oracle.GetBoardGraph(g, boardPath) != nil
}

Type guard

func validBoardPath(boardPath []string) bool {
    for _, p := range boardPath { if p == "" { return false } }
    return true
}

Try / catch

g2, _, err := d2oracle.Create(g, boardPath, key)
if err != nil && strings.Contains(err.Error(), "AST not found") {
    g, _, _ = recompileFromSource() // refresh AST, retry once
}

Prevention

When it happens

Trigger: Calling Create(g, boardPath, key) where GetBoardGraph found the board but ReplaceBoardNode cannot find a matching board node at boardPath in g.AST — e.g. boardPath entries don't exactly match the board names in the root AST, the board was removed from the AST after graph compilation, or boardPath contains wrong/renamed names.

Common situations: Passing a stale boardPath after the parent file was rewritten; typos or casing differences in layer/scenario/step names; mutating g.AST concurrently between GetBoardGraph and Create.

Related errors


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