gastownhall/beads · error

delete: count deps: %w

Error message

delete: count deps: %w

What it means

Wraps a failure from depRepo.ListByIssueIDs in countDeletedDependencies, which counts dependency edges that a deleteMany will remove across both the durable and wisp dependency planes. A table-not-exist error on the wisp pass is tolerated; this error fires for any other failure, including failures on the durable (first) pass.

Source

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

// (batch)` was run per 50-id batch, so an edge whose two ends fell in
// different batches matched twice. Keying by the edge itself removes that
// hazard rather than trading it for another: a row is counted once whether it
// is reached as somebody's outbound edge, somebody's inbound edge, or both.
func (u *issueUseCaseImpl) countDeletedDependencies(ctx context.Context, allIDs []string) (int, error) {
	if len(allIDs) == 0 {
		return 0, nil
	}
	seen := make(map[string]bool)
	for _, useWisps := range []bool{false, true} {
		edges, err := u.depRepo.ListByIssueIDs(ctx, allIDs, DepListOpts{
			Direction:     DepDirectionBoth,
			UseWispsTable: useWisps,
		})
		if err != nil {
			if useWisps && dberrors.IsTableNotExist(err) {
				continue
			}
			return 0, fmt.Errorf("delete: count deps: %w", err)
		}
		for _, side := range []map[string][]*types.Dependency{edges.Outgoing, edges.Incoming} {
			for _, list := range side {
				for _, dep := range list {
					if dep == nil {
						continue
					}
					// (source, target) is unique per table: the writer refuses a
					// second edge for a pair, retyping included.
					seen[fmt.Sprintf("%t\x00%s\x00%s", useWisps, dep.IssueID, dep.DependsOnID)] = true
				}
			}
		}
	}
	return len(seen), nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause and fix the underlying storage issue (lock, connectivity, corruption).
  2. Retry the delete when no other process is writing to the store.
  3. Use a fresh, non-canceled context for the operation.
  4. Run bd doctor to check database health if the error persists.
  5. Verify the dependencies (and wisp_dependencies) tables exist and match your driver's expected schema.
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check dependency tables are readable before delete
if _, err := usecase.GetDependencies(ctx, firstID); err != nil {
    return fmt.Errorf("dependency store unreadable: %w", err)
}

Type guard

func isCountDepsErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "delete: count deps:")
}

Try / catch

err := usecase.DeleteMany(ctx, ids)
if err != nil && strings.Contains(err.Error(), "delete: count deps:") {
    cause := errors.Unwrap(err)
    if !dberrors.IsTableNotExist(cause) {
        // retry with backoff or surface a storage-health problem
    }
}

Prevention

When it happens

Trigger: deleteMany of issues that have dependency edges, where ListByIssueIDs fails on either plane — locked database, dropped connection, context cancellation, or a driver-level SQL error.

Common situations: Deleting from a store while a Dolt compaction/GC runs; connection pool exhaustion under many parallel bd commands; canceled deletes; a dependency table corrupted or locked by another transaction.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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