gastownhall/beads · error
%w: %s cannot depend on itself
Error message
%w: %s cannot depend on itself
What it means
Returned when a dependency's issue and target are the same ID: an issue cannot depend on itself. This is a deliberate domain validation (wrapped around domain.ErrSelfDependency) so callers can use errors.Is to detect it, raised by CheckDependencyCycleInTx before the edge is inserted.
Source
Thrown at internal/storage/issueops/dependencies.go:443
AND s.is_blocked = 0
AND s.status <> 'closed' AND s.status <> 'pinned'
AND EXISTS (
SELECT 1 FROM (
SELECT id, status FROM %s WHERE id = ?
) AS t
WHERE t.status <> 'closed' AND t.status <> 'pinned'
)
`, sourceTable, targetTable), source, target)
return err
}
// CheckDependencyCycleInTx rejects self-dependencies and cycles across the
// combined blocks, conditional-blocks, and parent-child graph before insert.
// The caller may pass a restricted depTables list for a known storage bucket;
// nil uses all dependency tables.
func CheckDependencyCycleInTx(ctx context.Context, tx DBTX, dep *types.Dependency, depTables []string) error {
if dep.IssueID == dep.DependsOnID {
return fmt.Errorf("%w: %s cannot depend on itself", domain.ErrSelfDependency, dep.IssueID)
}
if !types.IsSchedulingEdge(dep.Type) {
return nil
}
wouldCycle, err := WouldCreateSchedulingCycleInTx(ctx, tx, dep.IssueID, dep.DependsOnID, depTables)
if err != nil {
return fmt.Errorf("failed to check for dependency cycle: %w", err)
}
if wouldCycle {
return domain.ErrDependencyCycle
}
return nil
}
// WouldCreateSchedulingCycleInTx reports whether adding issueID -> dependsOnID
// would close a cycle in the combined scheduling graph. It is shared by the
// classic and domain storage stacks so both traverse the same dependency types
// and typed target columns.View on GitHub (pinned to 71377f2769)
Solutions
- Compare dep.IssueID and dep.DependsOnID before calling and reject equal IDs with a clear client-side message
- Fix the caller that is populating both fields with the same value
- Use errors.Is(err, domain.ErrSelfDependency) to detect this case and skip the edge instead of failing the batch
Example fix
// before
if err := issueops.CheckDependencyCycleInTx(ctx, tx, dep, nil); err != nil {
return err
}
// after
if dep.IssueID == dep.DependsOnID {
return fmt.Errorf("skip self dependency %s", dep.IssueID)
}
if err := issueops.CheckDependencyCycleInTx(ctx, tx, dep, nil); err != nil {
return err
} Defensive patterns
Strategy: validation
Validate before calling
func validateNoSelfDep(dep types.Dependency) error {
if dep.IssueID == dep.DependsOnID {
return fmt.Errorf("issue %s cannot depend on itself", dep.IssueID)
}
return nil
} Type guard
func isSelfDependency(err error) bool {
return errors.Is(err, domain.ErrSelfDependency)
} Try / catch
if err := issueops.CheckDependencyCycleInTx(ctx, tx, dep, nil); err != nil {
if errors.Is(err, domain.ErrSelfDependency) {
return ErrSkipSelfDep // handle specifically, don't fail whole batch
}
return err
} Prevention
- Reject IssueID == DependsOnID at the API boundary before persisting
- Sanitize client-supplied parent/dep IDs against echoing the subject ID
- Use errors.Is(err, domain.ErrSelfDependency) for precise detection
- Add unit coverage for self-dep payloads in import/sync paths
When it happens
Trigger: Calling CheckDependencyCycleInTx (directly or via addDependencyInTx / PersistDependenciesWithOptionsResult) with a Dependency where IssueID == DependsOnID, e.g. a parent patch assigning an issue as its own parent, or a client echoing back an ID into both fields.
Common situations: Off-by-one or copy-paste bug in CLI/API payloads; syncing tools that map an ID to the wrong field; parent-patch applied to the issue itself.
Related errors
- %w: %s cannot depend on itself
- remove dep: sourceID and dependsOnID must not be empty
- reparent: %s cannot be its own parent
- %w: count edges status %q needs direction %q: an outbound ed
- no store is open for this workspace
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f92dbd6d18798272.
Report an issue: GitHub.