gastownhall/beads · error

gate: target step %q not found

Error message

gate: target step %q not found

What it means

After validating a gate's condition, applyGatesWithMap looks up Gate[i].Before in the workflow's step map to attach the gate label to that step. This error is returned when the target step ID does not exist in the steps list. The gate can only guard a step the workflow actually defines, so the library fails with the offending ID.

Source

Thrown at internal/formula/controlflow.go:533

	for _, gate := range compose.Gate {
		// Validate the gate rule
		if gate.Before == "" {
			return fmt.Errorf("gate: before is required")
		}
		if gate.Condition == "" {
			return fmt.Errorf("gate: condition is required")
		}

		// Validate the condition syntax
		_, err := ParseCondition(gate.Condition)
		if err != nil {
			return fmt.Errorf("gate: invalid condition %q: %w", gate.Condition, err)
		}

		// Find the target step
		step, ok := stepMap[gate.Before]
		if !ok {
			return fmt.Errorf("gate: target step %q not found", gate.Before)
		}

		// Add gate label for runtime evaluation using JSON for unambiguous parsing
		gateMeta := map[string]string{"condition": gate.Condition}
		gateJSON, _ := json.Marshal(gateMeta)
		gateLabel := fmt.Sprintf("gate:%s", string(gateJSON))
		step.Labels = appendUnique(step.Labels, gateLabel)
	}

	return nil
}

// ApplyControlFlow applies all control flow operators in the correct order:
// 1. Loops (expand iterations)
// 2. Branches (wire fork-join dependencies)
// 3. Gates (add condition labels)
//
// Returns a new steps slice. The original steps slice is not modified.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Correct the gate's `before` value to match an existing step ID exactly
  2. Confirm the target step is still defined and wasn't renamed or removed in a recent edit
  3. Check for exact-match issues (case, whitespace) between the gate's before value and the step ID
  4. If the step is produced by loop expansion, use the expanded step ID or gate the loop's source step

Example fix

// before (compose rules)
gate: [{before: deploy-prod, condition: "label:critical"}]
// steps define "deploy" only
// after
gate: [{before: deploy, condition: "label:critical"}]
Defensive patterns

Strategy: validation

Validate before calling

func validateGateTargets(steps []*Step, compose *ComposeRules) error {
	ids := make(map[string]bool, len(steps))
	for _, s := range steps {
		ids[s.ID] = true
	}
	for _, g := range compose.Gate {
		if g.Before != "" && !ids[g.Before] {
			return fmt.Errorf("gate targets unknown step %q", g.Before)
		}
	}
	return nil
}

Try / catch

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

Prevention

When it happens

Trigger: Calling ApplyGates or ApplyControlFlow with a gate whose Before field names a step absent from the steps slice — a typo, a renamed/deleted step, a step created by loop expansion under a different ID, or an ID with case/whitespace mismatch.

Common situations: Config referencing a step that was renamed in a refactor; gate written against a step in a different workflow file; copy-pasted gate rules not updated for the new workflow's step IDs; steps removed by an earlier edit while gates still point at them.

Related errors


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