gastownhall/beads · error · ErrSelfDependency
%w: %s cannot depend on itself
Error message
%w: %s cannot depend on itself
What it means
A self-dependency rejection: the dependency being added has IssueID equal to DependsOnID, so an issue/wisp would depend on itself. It wraps the sentinel ErrSelfDependency so callers can detect this case with errors.Is. This check runs before the cycle probe because a blocking self-edge would otherwise be misreported as a generic cycle error (#4547 F-1).
Source
Thrown at internal/storage/domain/dependency.go:284
func (u *dependencyUseCaseImpl) AddWispDependency(ctx context.Context, dep *types.Dependency, actor string) error {
return u.add(ctx, dep, actor, true)
}
func (u *dependencyUseCaseImpl) add(ctx context.Context, dep *types.Dependency, actor string, useWisp bool) error {
if dep == nil {
return fmt.Errorf("add dep: dep must not be nil")
}
if dep.IssueID == "" || dep.DependsOnID == "" {
return fmt.Errorf("add dep: IssueID and DependsOnID must be non-empty")
}
// 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).View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the DependsOnID points to a different issue than IssueID
- Detect the case programmatically with errors.Is(err, ErrSelfDependency) and surface a friendly message
- Fix scripts/UI to use distinct source and target IDs
- Sanitize imports to drop self-referencing edges
Example fix
// before
dep := &types.Dependency{IssueID: id, DependsOnID: id} // self-dep
err := uc.AddDependency(ctx, dep, actor)
// after
if id == targetID { return fmt.Errorf("cannot block %s with itself", id) }
dep := &types.Dependency{IssueID: id, DependsOnID: targetID}
err := uc.AddDependency(ctx, dep, actor) Defensive patterns
Strategy: validation
Validate before calling
if issueID == dependsOnID {
return fmt.Errorf("%s cannot depend on itself", issueID)
} Type guard
func isSelfDependency(err error) bool {
return errors.Is(err, storage.ErrSelfDependency)
} Try / catch
err := uc.AddDependency(ctx, dep, actor)
if err != nil {
if errors.Is(err, storage.ErrSelfDependency) {
return fmt.Errorf("friendly message: %s cannot block itself", dep.IssueID)
}
return err
} Prevention
- Use distinct variables for source and target IDs in scripts
- Guard UIs so the 'depends on' field cannot equal the current issue
- Filter self-edges during data imports
When it happens
Trigger: Calling AddDependency or AddWispDependency where dep.IssueID == dep.DependsOnID — typically the same ID variable passed to both fields, or a UI/script echoing one ID into both slots.
Common situations: Shell script reusing $ISSUE_ID for both arguments; form/UI pre-filling the 'depends on' field with the current issue; data import mapping the source column to both endpoints.
Related errors
- db: DependencySQLRepository.Insert: dep must not be nil
- db: DependencySQLRepository.Insert: IssueID must not be empt
- db: DependencySQLRepository.Insert: DependsOnID must not be
- db: DependencySQLRepository.ValidateBlockingHierarchy: dep m
- ErrVarValidation
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/08b88c21793ccc7a.
Report an issue: GitHub.