gastownhall/beads · error

type mismatch for formula.VarDef: expected string or table b

Error message

type mismatch for formula.VarDef: expected string or table but found %T

What it means

VarDef.UnmarshalTOML accepts either a plain string (variable name/shorthand) or a table with fields like type/default. Any other TOML type (integer, array, bool, etc.) triggers this error, which mirrors burntsushi/toml's Unmarshaler convention of naming the target type in the message.

Source

Thrown at internal/formula/types.go:190

		if req, ok := val["required"].(bool); ok {
			v.Required = req
		}
		if enum, ok := val["enum"].([]interface{}); ok {
			for _, e := range enum {
				if s, ok := e.(string); ok {
					v.Enum = append(v.Enum, s)
				}
			}
		}
		if pattern, ok := val["pattern"].(string); ok {
			v.Pattern = pattern
		}
		if typ, ok := val["type"].(string); ok {
			v.Type = typ
		}
		return nil
	default:
		return fmt.Errorf("type mismatch for formula.VarDef: expected string or table but found %T", data)
	}
}

// Step defines a work item to create when the formula is instantiated.
type Step struct {
	// ID is the unique identifier within this formula.
	// Used for dependency references and bond points.
	ID string `json:"id"`

	// Title is the issue title (supports {{variable}} substitution).
	Title string `json:"title"`

	// Description is the issue description (supports substitution).
	Description string `json:"description,omitempty"`

	// Notes are additional notes for the issue (supports substitution).
	Notes string `json:"notes,omitempty"`

View on GitHub (pinned to 71377f2769)

Solutions

  1. Open the TOML file and find the variables entry that is not a string or table.
  2. Quote string values: `variables = ["name"]` not `variables = [name]`.
  3. Use table form for full definitions: `[variables.myvar]` with `type`/`default` keys.
  4. Validate the formula TOML before loading (e.g. `bd formula validate` if available, or a TOML linter).

Example fix

// before
[[variables]]
port = 8080
// after
[[variables]]
name = "port"
type = "int"
default = 8080
Defensive patterns

Strategy: validation

Validate before calling

for i, v := range rawVars {
    switch v.(type) {
    case string, map[string]any:
    default:
        return fmt.Errorf("variables[%d] must be a string or table, got %T", i, v)
    }
}

Type guard

func isValidVarDef(data any) bool {
    switch data.(type) {
    case string, map[string]any:
        return true
    default:
        return false
    }
}

Try / catch

if err := tomlDecode(f, &formula); err != nil {
    if strings.Contains(err.Error(), "type mismatch for formula.VarDef") { /* point user at the variables section */ }
    return err
}

Prevention

When it happens

Trigger: UnmarshalTOML is invoked by the TOML decoder when a formula's variables section contains a value that is neither a string nor an inline table, e.g. `variables = [1, 2]` or a bare boolean.

Common situations: Hand-editing a formula TOML and writing a variable as a number or list instead of a string or `[variables.name]` table; forgetting quotes around a string value.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/699e9776abd9d7b9. Report an issue: GitHub.