gastownhall/beads · error

remove dep: classify source

Error message

remove dep: classify source: %w

What it means

Wraps a failure of depRepo.WispSourceIDs while RemoveDependencyBySource classifies whether the edge's source issue lives in the wisps table or the regular table. The classification determines which table the delete targets; when the lookup itself fails, the removal is aborted with this wrapper so the caller knows the failure happened before any delete was attempted.

Solutions

  1. Fix the underlying repository error surfaced by %w (connectivity, SQL, context).
  2. Confirm the database schema is current (bd doctor / migration status) since wisp classification reads the wisps table.
  3. Retry the removal once storage is healthy — no delete has run yet, so it is safe to re-invoke.
  4. If timeouts are the cause, increase the context deadline for large batch scripts.

Example fix

// before: assuming removal failed because the edge was missing
removed, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
    log.Println("edge not found")
}
// after: distinguish classification failure from not-found
removed, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
    log.Printf("removal aborted before delete: %v", err) // storage issue
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-verify storage reachability before batch removal
if err := db.PingContext(ctx); err != nil {
    return fmt.Errorf("storage unreachable, aborting removals: %w", err)
}

Try / catch

_, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
        return retryWithLongerDeadline()
    }
    return fmt.Errorf("classification/read failure, nothing deleted: %w", err)
}

Prevention

When it happens

Trigger: RemoveDependencyBySource calls WispSourceIDs(ctx, []string{sourceID}) and the repo query fails — DB unreachable, SQL error, context canceled, or Dolt transaction/lock error while scanning the wisps classification.

Common situations: Database connection dropped mid-command; context deadline exceeded during batch removals; Dolt server error or schema mismatch after a version upgrade; read-only replica used for the classification query.

Related errors


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

Appendix: source

Thrown at internal/storage/domain/dependency.go:352

	return u.removeDep(ctx, wispID, dependsOnID, actor, true)
}

// RemoveDependencyBySource removes one edge from the plane its SOURCE lives in
// and reports whether there was an edge to remove.
//
// 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
}

View on GitHub (pinned to 71377f2769)