gastownhall/beads · error
remove dep %s -> %s: %w
Error message
remove dep %s -> %s: %w
What it means
Wraps a failure of depRepo.Delete when RemoveDependencyBySource actually removes the edge (sourceID -> dependsOnID) from the table matching the source's plane (wisp or regular). The edge's existence is reported by the returned res.Found boolean, so this error always means the delete operation itself failed — e.g. a storage error — not that the edge was absent.
Source
Thrown at internal/storage/domain/dependency.go:357
//
// It is the source-routed twin of AddDependencies, and exists for the same
// reason: `bd dep remove` takes whatever id the caller names, and pinning the
// removal to the durable table means failing to remove an edge whose source is
// a wisp while reporting that it was never there (bd-yby99.17). The delete IS
// the verdict, the way the store-backed body reads it off RemoveDependencyInTx
// rather than from a separate lookup.
func (u *dependencyUseCaseImpl) RemoveDependencyBySource(ctx context.Context, sourceID, dependsOnID, actor string) (bool, error) {
if sourceID == "" || dependsOnID == "" {
return false, fmt.Errorf("remove dep: sourceID and dependsOnID must not be empty")
}
wispSources, err := u.depRepo.WispSourceIDs(ctx, []string{sourceID})
if err != nil {
return false, fmt.Errorf("remove dep: classify source: %w", err)
}
_, sourceIsWisp := wispSources[sourceID]
res, err := u.depRepo.Delete(ctx, sourceID, dependsOnID, actor, DepInsertOpts{UseWispsTable: sourceIsWisp, EmitEvent: true})
if err != nil {
return false, fmt.Errorf("remove dep %s -> %s: %w", sourceID, dependsOnID, err)
}
return res.Found, nil
}
func (u *dependencyUseCaseImpl) removeDep(ctx context.Context, sourceID, dependsOnID, actor string, useWisp bool) error {
if sourceID == "" || dependsOnID == "" {
return fmt.Errorf("remove dep: sourceID and dependsOnID must not be empty")
}
if _, err := u.depRepo.Delete(ctx, sourceID, dependsOnID, actor, DepInsertOpts{UseWispsTable: useWisp, EmitEvent: true}); err != nil {
return fmt.Errorf("remove dep %s -> %s: %w", sourceID, dependsOnID, err)
}
return nil
}
func (u *dependencyUseCaseImpl) Reparent(ctx context.Context, childID, newParentID, actor string) error {
return u.reparent(ctx, childID, newParentID, actor, false)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped cause to identify the storage failure (lock, connection, constraint).
- Retry the delete — it is idempotent; res.Found tells you whether the edge existed afterward.
- Check for concurrent processes holding locks on the same issue rows and serialize the work.
- Verify the backend is writable and healthy (bd doctor) before bulk-removal scripts.
Example fix
// before
removed, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
return err
}
// after
removed, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
if isTransient(err) {
return retryRemove(src, dst, actor) // delete is idempotent
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the edge exists first (optional; res.Found also reports it)
rows, err := db.QueryContext(ctx,
`SELECT 1 FROM dependencies WHERE issue_id=? AND depends_on_id=?`, src, dst)
if err == nil && !rows.Next() {
return nil // nothing to remove
} Try / catch
found, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
if isTransientStorageErr(err) {
return retryRemove(src, dst, actor) // delete is idempotent
}
return fmt.Errorf("delete failed for %s -> %s: %w", src, dst, err)
}
if !found {
log.Println("edge already absent")
} Prevention
- Treat deletes as idempotent and retry transient failures
- Serialize concurrent removals touching the same issues
- Check for Dolt lock conflicts during sync before bulk deletes
- Use RemoveDependencyBySource (source-routed) to avoid plane mismatches
When it happens
Trigger: RemoveDependencyBySource has classified the source and calls depRepo.Delete with UseWispsTable set accordingly; the delete fails due to DB connection loss, SQL constraint/lock error, Dolt transaction conflict, or context cancellation mid-write.
Common situations: Concurrent writers deleting/updating the same edge; Dolt lock conflict during sync; database went read-only or disk full; connection pool exhausted during large batch dep-removal scripts.
Related errors
- loading proto: %w
- loading subgraph for %s: %w
- failed to delete child %s: %w
- add dep: cycle check: %w
- add dep: insert: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/457469f8ea43c232.
Report an issue: GitHub.