gastownhall/beads · error

final cycle check failed (no edges added): %w

Error message

final cycle check failed (no edges added): %w

What it means

The batch applier's end gate re-checks for dependency cycles through all edges applied in the batch; this error wraps a failure of that check call itself (CycleThroughEdges returned an error), not the detection of an actual cycle. Because nothing was committed ('no edges added'), the whole batch can be safely retried once the cause is fixed.

Source

Thrown at internal/storage/uow/batch_applier.go:510

}

// runEndGate re-validates the graph the WHOLE request produced, through the
// same two repository checks the per-edge path runs, so this leg and the store
// legs answer the same refusal to the same request. It is never skippable.
func (r *uowApplyRun) runEndGate(ctx context.Context) error {
	if len(r.edges) == 0 {
		return nil
	}
	var pairs [][2]string
	for _, applied := range r.edges {
		if !types.IsSchedulingEdge(applied.dep.Type) {
			continue
		}
		pairs = append(pairs, [2]string{applied.dep.IssueID, applied.dep.DependsOnID})
	}
	cyclePath, err := r.uw.DependencyUseCase().CycleThroughEdges(ctx, pairs)
	if err != nil {
		return fmt.Errorf("final cycle check failed (no edges added): %w", err)
	}
	if cyclePath != "" {
		return domain.NewCycleError("dependency cycle would be created: %s (no edges added; run 'bd dep cycles' for analysis)", cyclePath)
	}
	for _, applied := range r.edges {
		if storageissueops.IsExternalDepTarget(applied.dep.IssueID, applied.dep.DependsOnID) {
			continue
		}
		if err := r.uw.DependencyUseCase().ValidateBlockingHierarchy(ctx, applied.dep); err != nil {
			return &publicops.ItemError{
				Index:   applied.index,
				Kind:    publicops.ItemDepAdd,
				IssueID: applied.dep.IssueID,
				Err:     err,
			}
		}
	}
	return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the batch after connectivity is restored — nothing was committed
  2. Reduce batch size if the cycle check query is timing out
  3. Check DB health/logs for the wrapped root error (connection refused, lock wait, etc.)
  4. Split the batch into smaller groups so the end-gate query has fewer pairs to analyze

Example fix

// before
batch of 5000 items -> end-gate cycle query times out
// after
split into chunks of e.g. 200 items and apply sequentially
Defensive patterns

Strategy: retry

Validate before calling

// pre-check cycles client-side before submitting dep-add batches
if path := detectCycleLocally(existingEdges, newPairs); path != "" {
    return fmt.Errorf("batch would create cycle: %s", path)
}

Try / catch

err := applyBatch(ctx, batch)
if err != nil && strings.Contains(err.Error(), "final cycle check failed") {
    // safe to retry: nothing was committed ('no edges added')
    return retryWithBackoff(func() error { return applyBatch(ctx, batch) })
}

Prevention

When it happens

Trigger: runEndGate calls DependencyUseCase().CycleThroughEdges(ctx, pairs) after all items applied and the underlying query fails — DB connectivity loss, transaction/lock errors, timeout on large pair sets.

Common situations: Database connection dropped mid-batch (timeout, restart); Dolt transaction aborted; oversized batches making the cycle query time out.

Related errors


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