gastownhall/beads · error

delete: list dependents: %w

Error message

delete: list dependents: %w

What it means

This error wraps a failure from depRepo.ListByIssueIDs with Direction=DepDirectionIn (incoming dependencies) in externalDependents, which finds direct dependents of the ids being deleted that are not themselves in the delete set. The wisp-table variant tolerates a missing wisps table, but a failure on the main dependencies table is fatal for the delete flow.

Source

Thrown at internal/storage/domain/issue_delete.go:232

	if err := u.issueRepo.RecomputeIsBlocked(ctx, affectedIssues, affectedWisps); err != nil {
		return result, fmt.Errorf("delete: recompute is_blocked: %w", err)
	}

	return result, nil
}

// externalDependents finds the direct dependents of each id in ids that are
// not themselves in ids, across both the issue and wisp dependency tables.
// The result maps deletion-set id -> external dependent ids (unsorted).
func (u *issueUseCaseImpl) externalDependents(ctx context.Context, ids []string) (map[string][]string, error) {
	idSet := make(map[string]bool, len(ids))
	for _, id := range ids {
		idSet[id] = true
	}

	issueRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionIn})
	if err != nil {
		return nil, fmt.Errorf("delete: list dependents: %w", err)
	}
	wispRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionIn, UseWispsTable: true})
	if err != nil && !dberrors.IsTableNotExist(err) {
		return nil, fmt.Errorf("delete: list wisp dependents: %w", err)
	}

	out := map[string][]string{}
	seen := map[string]map[string]bool{}
	for _, res := range []DepBulkResult{issueRes, wispRes} {
		for target, deps := range res.Incoming {
			for _, d := range deps {
				if d.IssueID == "" || idSet[d.IssueID] {
					continue
				}
				if seen[target] == nil {
					seen[target] = map[string]bool{}
				}
				if seen[target][d.IssueID] {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped driver error — fix connectivity or the underlying storage issue first.
  2. Reduce batch size if the id list is very large, and call delete in chunks.
  3. Retry the operation; the query is read-only and safe to re-run.
  4. Verify the dependencies table exists and is migrated in this database.
Defensive patterns

Strategy: retry

Validate before calling

// Validate ids before the call:
for _, id := range ids {
    if id == "" {
        return errors.New("empty issue id in delete set")
    }
}
if err := store.PingContext(ctx); err != nil {
    return fmt.Errorf("db unreachable: %w", err)
}

Type guard

var dbErr *dberrors.DBError
if errors.As(err, &dbErr) {
    // inspect dbErr.Code to distinguish connectivity vs scan errors
}

Try / catch

_, err := uc.DeleteIssues(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "delete: list dependents") {
    // read-only phase failed; safe to retry with backoff
    return retryWithBackoff(func() error { _, err := uc.DeleteIssues(ctx, ids, opts); return err })
}

Prevention

When it happens

Trigger: deleteMany -> externalDependents when the incoming-dependency bulk query on the regular dependencies table fails: DB unreachable, malformed ids, query timeout, or driver-level scan error.

Common situations: Database connection loss during delete; extremely long id lists producing oversized IN clauses; corrupted dependency rows failing to scan into types.Dependency.

Related errors


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