d2lang/d2 · error

failed to recompile: %s %w

Error message

failed to recompile:
%s
%w

What it means

After AST edits, recompile serializes the mutated AST with d2format and recompiles it via d2compiler.Compile. If the edited AST is no longer valid D2 (malformed keys, broken references, invalid values), the compiler error is wrapped as 'failed to recompile:\n<formatted source>\n<cause>' so the caller can see both the exact post-edit source and the compiler diagnostics.

Source

Thrown at d2oracle/edit.go:321

	// We don't want this to be underscore-resolved scope. We want to ignore underscores
	var scopeak []string
	if fromScope != g.Root {
		scopek, err := d2parser.ParseKey(fromScope.AbsID())
		if err != nil {
			return nil, err
		}
		scopeak = d2graph.Key(scopek)
	}
	return pathFromScopeKey(g, key, scopeak)
}

func recompile(g *d2graph.Graph) (*d2graph.Graph, error) {
	s := d2format.Format(g.AST)
	g2, _, err := d2compiler.Compile(g.AST.Range.Path, strings.NewReader(s), &d2compiler.CompileOptions{
		FS: g.FS,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err)
	}
	return g2, nil
}

// TODO merge flat styles
func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string) error {
	if tag != nil {
		if hasSpace(*tag) {
			return fmt.Errorf("spaces are not allowed in blockstring tags")
		}
	}

	mk, err := d2parser.ParseMapKey(key)
	if err != nil {
		return err
	}

	if len(mk.Edges) > 1 {

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Read the embedded post-edit source and compiler message in the wrapped error to find the offending line/key.
  2. Fix the key/value passed to the oracle (correct syntax, valid identifier, type-correct value).
  3. Remove or update references (edges/connections) to deleted objects before/after the mutation.
  4. Validate the desired value against D2 docs (reserved keywords, style value types) before calling Set/Create.

Example fix

// before
g2, err := d2oracle.Set(g, nil, "shape", nil, &str) // invalid value -> recompile fails
// after
g2, err := d2oracle.Set(g, nil, "x.shape", nil, &str) // valid key + valid scalar value
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check key/value before oracle calls
if strings.ContainsAny(key, "\"") { return errors.New("invalid key") }
if tag != nil && hasSpace(*tag) { return errors.New("invalid tag") }

Type guard

func oracleErrRecompile(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to recompile:")
}

Try / catch

g2, err := d2oracle.Set(g, nil, key, tag, value)
if oracleErrRecompile(err) {
    var src, cause string
    fmt.Sscanf(err.Error(), "failed to recompile:\n%[^\n]\n%s", &src, &cause) // inspect post-edit source
    log.Printf("oracle produced invalid D2: %v", err)
}

Prevention

When it happens

Trigger: Any oracle mutation (Create, Set, ReconnectEdge, Delete, deleteReserved, deleteObjField) that leaves the AST in a state the D2 compiler rejects — e.g. setting a reserved/invalid key value, an edge key referencing a removed object, or malformed key syntax accepted by the parser but not the compiler.

Common situations: Setting values of wrong types (e.g. non-numeric into numeric fields); creating keys that collide with reserved words; deleting an object while edges still reference it; invalid blockstring content.

Related errors


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