gastownhall/beads · error

branch: parallel step %q not found

Error message

branch: parallel step %q not found

What it means

applyBranchesWithMap validates every step ID listed in a branch rule's Steps slice against the workflow's step map before wiring fork-join dependencies. This error is returned when a parallel-step ID referenced in a `branch` compose rule does not match any step defined in the steps list. It exists to fail fast with the offending ID instead of silently skipping dependency wiring.

Source

Thrown at internal/formula/controlflow.go:464

			return fmt.Errorf("branch: from is required")
		}
		if len(branch.Steps) == 0 {
			return fmt.Errorf("branch: steps is required")
		}
		if branch.Join == "" {
			return fmt.Errorf("branch: join is required")
		}

		// Verify all steps exist
		if _, ok := stepMap[branch.From]; !ok {
			return fmt.Errorf("branch: from step %q not found", branch.From)
		}
		if _, ok := stepMap[branch.Join]; !ok {
			return fmt.Errorf("branch: join step %q not found", branch.Join)
		}
		for _, stepID := range branch.Steps {
			if _, ok := stepMap[stepID]; !ok {
				return fmt.Errorf("branch: parallel step %q not found", stepID)
			}
		}

		// Add dependencies: branch steps depend on 'from'
		for _, stepID := range branch.Steps {
			step := stepMap[stepID]
			step.Needs = appendUnique(step.Needs, branch.From)
		}

		// Add dependencies: 'join' depends on all branch steps
		joinStep := stepMap[branch.Join]
		for _, stepID := range branch.Steps {
			joinStep.Needs = appendUnique(joinStep.Needs, stepID)
		}
	}

	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the workflow's step definitions and correct the parallel step ID in the branch rule's steps list so it matches a defined step ID exactly
  2. Verify the step wasn't renamed or removed by a recent edit and update the branch rule accordingly
  3. Check for case or whitespace mismatches between the rule and the step ID (matching is exact string lookup)
  4. If the step should exist, confirm ApplyControlFlow's loop expansion (which runs before branches) isn't renaming it

Example fix

// before (compose rules)
branch: [{from: build, steps: [test-unit, test-int], join: report}]
// steps define "test-integration", not "test-int"
// after
branch: [{from: build, steps: [test-unit, test-integration], join: report}]
Defensive patterns

Strategy: validation

Validate before calling

func validateBranchSteps(steps []*Step, compose *ComposeRules) error {
	ids := make(map[string]bool, len(steps))
	for _, s := range steps {
		ids[s.ID] = true
	}
	for _, b := range compose.Branch {
		for _, id := range b.Steps {
			if !ids[id] {
				return fmt.Errorf("branch references unknown parallel step %q", id)
			}
		}
	}
	return nil
}

Try / catch

if _, err := formula.ApplyControlFlow(steps, compose); err != nil {
	var missing string
	if n, _ := fmt.Sscanf(err.Error(), "branch: parallel step %q not found", &missing); n == 1 {
		return fmt.Errorf("workflow config error: branch lists step %q; defined steps: %v", missing, stepIDs(steps))
	}
	return err
}

Prevention

When it happens

Trigger: Calling ApplyBranches or ApplyControlFlow with a ComposeRules whose Branch[i].Steps contains a stepID that is absent from the steps slice — typically a typo in the step ID, a step defined with a different ID, or a step removed/renamed while the branch rule still lists the old ID.

Common situations: Hand-edited workflow YAML/JSON where a branch rule references a step that was renamed; copy-pasting a branch block from another workflow; ID case mismatch (IDs are matched exactly); a loop expansion removing or renaming the target step.

Related errors


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