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

  1. Inspect the wrapped cause to identify the storage failure (lock, connection, constraint).
  2. Retry the delete — it is idempotent; res.Found tells you whether the edge existed afterward.
  3. Check for concurrent processes holding locks on the same issue rows and serialize the work.
  4. 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

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


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