gastownhall/beads · error

add dep: cycle check: %w

Error message

add dep: cycle check: %w

What it means

Wraps a repository failure that occurred while running the cycle-detection probe (depRepo.HasCycle) during dependency creation. Beads refuses scheduling-type dependency edges (e.g. blocks/blocked-by) that would form a cycle, and this wrapper only fires when the probe itself errored — not when a cycle was actually found (that returns the sentinel ErrDependencyCycle instead). It means the cycle check could not complete, typically due to a storage/backend problem.

Source

Thrown at internal/storage/domain/dependency.go:297

	// Self-dependency guard mirrors issueops.CheckDependencyCycleInTx: it is
	// checked BEFORE the cycle probe and for ALL dep types, and emits the
	// dedicated self-dep message. A blocking self-edge otherwise trips HasCycle
	// and would report the wrong (cycle) error (#4547 F-1).
	if dep.IssueID == dep.DependsOnID {
		return fmt.Errorf("%w: %s cannot depend on itself", ErrSelfDependency, dep.IssueID)
	}
	if err := u.depRepo.ValidateBlockingHierarchy(ctx, dep); err != nil {
		var hierarchyConflict *DependencyHierarchyConflictError
		if errors.As(err, &hierarchyConflict) {
			return err
		}
		return fmt.Errorf("add dep: hierarchy check: %w", err)
	}

	if types.IsSchedulingEdge(dep.Type) {
		cycle, err := u.depRepo.HasCycle(ctx, dep.IssueID, dep.DependsOnID)
		if err != nil {
			return fmt.Errorf("add dep: cycle check: %w", err)
		}
		if cycle {
			// Match the embedded store's user-facing wording verbatim (no ids
			// prefix) so gc code that string-matches this error behaves the same
			// on both plumbings (#4547 F-1).
			return ErrDependencyCycle
		}
	}

	if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp, HierarchyValidated: true, CycleValidated: true, EmitEvent: true}); err != nil {
		// The retype conflict is a user-facing error whose message already
		// matches embedded verbatim; pass it through unwrapped so the CLI does
		// not prepend "add dep: insert:" (#4547 F-1). The endpoint-existence
		// refusals are here for the same reason.
		var conflict *DependencyTypeConflictError
		if errors.As(err, &conflict) {
			return err
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped %w cause with errors.Unwrap / %+v to see the underlying repository error and fix that root cause first.
  2. Verify the database backend is reachable and healthy (bd doctor, or reconnect the Dolt server).
  3. Retry the operation once the storage layer is healthy — the check is read-only and safe to re-run.
  4. If the cause is a context timeout, re-run with a longer deadline or smaller batch instead of bypassing the check.

Example fix

// before: swallowing the wrapped cause
if err := uc.AddDependency(ctx, dep, actor); err != nil {
    log.Println("dep add failed")
}
// after: surface and classify the underlying cause
if err := uc.AddDependency(ctx, dep, actor); err != nil {
    if errors.Is(err, domain.ErrDependencyCycle) {
        log.Println("cycle rejected")
    } else {
        log.Printf("cycle check/storage failure: %v", err)
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if dep == nil || dep.IssueID == "" || dep.DependsOnID == "" || dep.IssueID == dep.DependsOnID {
    return fmt.Errorf("invalid dependency before add")
}
if !types.IsSchedulingEdge(dep.Type) {
    // cycle check is skipped for non-scheduling edges
    _ = dep
}

Type guard

func isSchedulingDep(dep *types.Dependency) bool {
    return dep != nil && dep.IssueID != "" && dep.DependsOnID != "" && types.IsSchedulingEdge(dep.Type)
}

Try / catch

if err := uc.AddDependency(ctx, dep, actor); err != nil {
    switch {
    case errors.Is(err, domain.ErrDependencyCycle):
        // cycle rejected — not this error
    case errors.Is(err, context.DeadlineExceeded):
        // storage timeout during cycle check; retry with longer deadline
    default:
        return fmt.Errorf("cycle check storage failure: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling AddDependency or AddWispDependency with a dep whose Type passes types.IsSchedulingEdge (e.g. blocks, parent-child scheduling semantics), where the underlying HasCycle query to the Dolt/sql repo fails — DB connection dropped, SQL error, context canceled mid-query, or lock/timeout in the dependency graph query.

Common situations: Database unavailable or restarting while a script batches bd dep add calls; context deadline exceeded on large dependency graphs; transient Dolt server errors during sync; running commands against a stale or locked .beads database.

Related errors


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