gastownhall/beads · error

formula validation failed: - %s

Error message

formula validation failed:
  - %s

What it means

The formula Validate method aggregates all structural problems it finds (missing IDs, missing compose hooks' attach fields, unknown references, etc.) into a single error listing each problem prefixed with " - ". It is the top-level validation failure for a formula document, not a single specific defect.

Source

Thrown at internal/formula/types.go:677

			if bp.BeforeStep != "" {
				if _, exists := stepIDLocations[bp.BeforeStep]; !exists {
					errs = append(errs, fmt.Sprintf("compose.bond_points[%d] (%s): before_step references unknown step %q", i, bp.ID, bp.BeforeStep))
				}
			}
		}

		for i, hook := range f.Compose.Hooks {
			if hook.Trigger == "" {
				errs = append(errs, fmt.Sprintf("compose.hooks[%d]: trigger is required", i))
			}
			if hook.Attach == "" {
				errs = append(errs, fmt.Sprintf("compose.hooks[%d]: attach is required", i))
			}
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("formula validation failed:\n  - %s", strings.Join(errs, "\n  - "))
	}

	return nil
}

// collectChildIDs recursively collects step IDs from children.
// idLocations maps ID -> location where first defined (for better duplicate error messages).
func collectChildIDs(children []*Step, idLocations map[string]string, errs *[]string, prefix string) {
	for i, child := range children {
		childPrefix := fmt.Sprintf("%s.children[%d]", prefix, i)
		if child.ID == "" {
			*errs = append(*errs, fmt.Sprintf("%s: id is required", childPrefix))
			continue
		}
		if firstLoc, exists := idLocations[child.ID]; exists {
			*errs = append(*errs, fmt.Sprintf("%s: duplicate id %q (first defined at %s)", childPrefix, child.ID, firstLoc))
		} else {
			idLocations[child.ID] = childPrefix

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read each bullet in the error message; each names the offending path (e.g. compose.hooks[0]).
  2. Fix the indicated fields — e.g. add `attach` to each compose hook.
  3. Re-run validation until the error disappears; the list regenerates fresh each time.
  4. Compare against a known-good example formula for the current schema.

Example fix

// before (TOML)
[[compose.hooks]]
command = "setup"
// after
[[compose.hooks]]
command = "setup"
attach = "step-1"
Defensive patterns

Strategy: validation

Validate before calling

if err := formula.Validate(); err != nil {
    // err lists every problem, one per line prefixed by "  - "
    lines := strings.Split(err.Error(), "\n")
    for _, l := range lines[1:] { log.Printf("formula problem:%s", strings.TrimPrefix(l, "  - ")) }
    return err
}

Try / catch

if verr := f.Validate(); verr != nil && strings.Contains(verr.Error(), "formula validation failed") { /* iterate bullets; fix each named path */ }

Prevention

When it happens

Trigger: Calling Validate (public method on the formula type) on a formula whose checks appended entries to errs — e.g. a compose hook with no `attach` field — and at least one problem exists.

Common situations: Loading a formula after a schema/version change, hand-editing a formula and omitting required fields, or importing a formula written for an older format.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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