gastownhall/beads · error

remove dep: sourceID and dependsOnID must not be empty

Error message

remove dep: sourceID and dependsOnID must not be empty

What it means

A guard error from RemoveDependencyBySource: one or both of the edge endpoint IDs (sourceID, dependsOnID) is the empty string. The use case cannot address, classify, or delete an edge without both endpoints, so it refuses before touching the repository. It is a caller-input validation failure, not a storage problem.

Source

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

	return u.removeDep(ctx, issueID, dependsOnID, actor, false)
}

func (u *dependencyUseCaseImpl) RemoveWispDependency(ctx context.Context, wispID, dependsOnID, actor string) error {
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check both IDs for emptiness at the call site before invoking RemoveDependencyBySource.
  2. Trace where the empty ID comes from — fix the upstream lookup/parse so a real issue ID is produced.
  3. If the edge may not exist, look up the issue first and skip the removal call rather than passing a blank ID.
  4. Return a clear validation error to your user naming which ID was missing.

Example fix

// before
removed, err := uc.RemoveDependencyBySource(ctx, issueID, depID, actor)
// after
if issueID == "" || depID == "" {
    return fmt.Errorf("cannot remove dep: issueID=%q depID=%q", issueID, depID)
}
removed, err := uc.RemoveDependencyBySource(ctx, issueID, depID, actor)
Defensive patterns

Strategy: validation

Validate before calling

func validateEdgeIDs(sourceID, dependsOnID string) error {
    if sourceID == "" {
        return fmt.Errorf("sourceID is empty")
    }
    if dependsOnID == "" {
        return fmt.Errorf("dependsOnID is empty")
    }
    return nil
}
if err := validateEdgeIDs(sourceID, dependsOnID); err != nil {
    return err
}
removed, err := uc.RemoveDependencyBySource(ctx, sourceID, dependsOnID, actor)

Type guard

func hasEdgeIDs(sourceID, dependsOnID string) bool {
    return sourceID != "" && dependsOnID != ""
}

Try / catch

if err := validateEdgeIDs(sourceID, dependsOnID); err != nil {
    return fmt.Errorf("cannot remove dep: %w", err)
}
_, err := uc.RemoveDependencyBySource(ctx, sourceID, dependsOnID, actor)

Prevention

When it happens

Trigger: Calling RemoveDependencyBySource(ctx, "", dependsOnID, actor) or with an empty dependsOnID — typically because an upstream lookup returned no ID, a parsed CLI argument was blank, or a struct field was never populated before the call.

Common situations: Scripting `bd dep remove` with a variable that failed to resolve; parsing issue IDs from output where a column was empty; constructing the call from JSON where a key was missing and Go zero-values the string.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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