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
- Inspect the wrapped driver error; retry the removal transaction if transient.
- Verify the dependency table still has type and metadata columns (run migrations).
- Check the DB user's SELECT grants on both dependencies and wisp_dependencies.
- 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
- Treat no-op removals (missing edge) as success — the library already returns false, nil.
- Keep the events/type/metadata columns migrated on both dependency tables.
- Set busy_timeout to tolerate concurrent writers during dep removal.
- Verify wisp status is fresh before removals; routing depends on it.
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
- count inbound dependencies from %s: %w
- retarget inbound dependencies to wisp in %s for %s: %w
- retarget inbound dependencies to issue in %s for %s: %w
- check retarget collision in %s: %w
- check rename collision in %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/be70560b940cebc0.
Report an issue: GitHub.