gastownhall/beads · error

lookup dependency type for %s -> %s: %w

Error message

lookup dependency type for %s -> %s: %w

What it means

This error wraps a non-ErrNoRows failure from the SELECT that fetches a dependency edge's type and metadata before deleting it in removeDependencyInTx. A missing edge is a normal no-op (returns false, nil); this error means the lookup query itself failed at the driver level.

Source

Thrown at internal/storage/issueops/dependencies.go:938

func RemoveDependencyInTx(ctx context.Context, tx *sql.Tx, issueID, dependsOnID, actor string, emitEvent bool) (bool, error) {
	return removeDependencyInTx(ctx, tx, issueID, dependsOnID, actor, emitEvent, nil)
}

func removeDependencyInTx(ctx context.Context, tx *sql.Tx, issueID, dependsOnID, actor string, emitEvent bool, recomputeResult *RecomputeIsBlockedResult) (bool, error) {
	isWisp := IsActiveWispInTx(ctx, tx, issueID)
	_, _, eventTable, depTable := WispTableRouting(isWisp)

	// Capture the row's type before deleting so we can dispatch the right
	// affected-set helper. If no row matches, treat as a no-op.
	var depType, depMetadata string
	row := tx.QueryRowContext(ctx, fmt.Sprintf(
		`SELECT type, metadata FROM %s WHERE issue_id = ? AND %s = ?`, depTable, DepTargetExpr),
		issueID, dependsOnID)
	if err := row.Scan(&depType, &depMetadata); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		return false, fmt.Errorf("lookup dependency type for %s -> %s: %w", issueID, dependsOnID, err)
	}

	if _, err := tx.ExecContext(ctx, fmt.Sprintf(
		`DELETE FROM %s WHERE issue_id = ? AND %s = ?`, depTable, DepTargetExpr),
		issueID, dependsOnID); err != nil {
		return false, fmt.Errorf("remove dependency: %w", err)
	}

	// The lookup above returned early when no row matched, so reaching here means
	// an edge was actually deleted. Record the dependency_removed event on the
	// source issue's event table for bd CLI / library history observers — but only
	// when emitEvent is set, so structural removes stay silent (parity with the
	// proxied repo and with the symmetric AddDependencyInTx EmitEvent gate).
	eventWritten := false
	if emitEvent {
		if err := RecordEventInTable(ctx, tx, eventTable, issueID, types.EventDependencyRemoved, actor,
			fmt.Sprintf("Removed dependency on %s", dependsOnID)); err != nil {
			return false, fmt.Errorf("record dependency_removed event: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped driver error; retry the removal transaction if transient.
  2. Verify the dependency table still has type and metadata columns (run migrations).
  3. Check the DB user's SELECT grants on both dependencies and wisp_dependencies.
  4. Confirm the issue's wisp/issue status is current, since table routing depends on it.

Example fix

// before: removal fails on locked database
ok, err := store.RemoveDependencyInTx(ctx, tx, "bd-1", "bd-2", actor, true)
// after: wait for lock release / busy_timeout before retrying
db.Exec("PRAGMA busy_timeout=5000")
ok, err = store.RemoveDependencyInTx(ctx, tx, "bd-1", "bd-2", actor, true)
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the edge exists and its table is readable before removal
isWisp := isActiveWisp(ctx, db, issueID)
table := "dependencies"; if isWisp { table = "wisp_dependencies" }
var t string
err := db.QueryRow("SELECT type FROM "+table+" WHERE issue_id=? AND COALESCE(depends_on_issue_id, depends_on_wisp_id, depends_on_external)=?", issueID, dependsOnID).Scan(&t)
if errors.Is(err, sql.ErrNoRows) { return nil } // nothing to remove
if err != nil { return fmt.Errorf("precheck failed: %w", err) }

Type guard

func isDepLookupFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "lookup dependency type for")
}

Try / catch

removed, err := store.RemoveDependency(ctx, issueID, dependsOnID, actor)
if isDepLookupFailure(err) && isTransient(errors.Unwrap(err)) {
    removed, err = withBackoff(3, func() (bool, error) { return store.RemoveDependency(ctx, issueID, dependsOnID, actor) })
}

Prevention

When it happens

Trigger: RemoveDependencyInTx / ApplyParentPatch calls removeDependencyInTx; the SELECT type, metadata FROM <depTable> WHERE issue_id=? AND <target>=? fails with a driver error other than sql.ErrNoRows — wrong depTable routing, connection loss, permissions, corrupted table.

Common situations: Removing a dependency while the DB is locked by another writer; the wisp-routing decision picked a table the user cannot read; Dolt server hiccup mid-transaction; schema drift removing type/metadata columns.

Related errors


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