gastownhall/beads · error

db: DependencySQLRepository.Delete: %s -> %s: %w

Error message

db: DependencySQLRepository.Delete: %s -> %s: %w

What it means

This error wraps the driver error from the DELETE statement removing the dependency edge row. The type lookup confirmed the edge existed, but the actual DELETE FROM <table> WHERE issue_id = ? AND <target> = ? failed.

Source

Thrown at internal/storage/domain/db/dependency.go:354

	var depType, depMetadata string
	//nolint:gosec // G201: table and depTargetExpr are hardcoded constants
	err := r.runner.QueryRowContext(ctx,
		fmt.Sprintf("SELECT type, metadata FROM %s WHERE issue_id = ? AND %s = ?", table, depTargetExpr),
		issueID, dependsOnID,
	).Scan(&depType, &depMetadata)
	switch {
	case errors.Is(err, sql.ErrNoRows):
		return domain.DepDeleteResult{Found: false}, nil
	case err != nil:
		return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: lookup type %s -> %s: %w", issueID, dependsOnID, err)
	}

	//nolint:gosec // G201: table and depTargetExpr are hardcoded constants
	if _, err := r.runner.ExecContext(ctx,
		fmt.Sprintf("DELETE FROM %s WHERE issue_id = ? AND %s = ?", table, depTargetExpr),
		issueID, dependsOnID,
	); err != nil {
		return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: %s -> %s: %w", issueID, dependsOnID, err)
	}

	// The type lookup above returned Found:false when no edge existed, so reaching
	// here means a row was deleted — record the dependency_removed event on the
	// source's event table, matching the embedded/issueops RemoveDependencyInTx path.
	// Gated on EmitEvent so only the explicit `bd dep remove` verb emits.
	if opts.EmitEvent {
		if err := r.events.Record(ctx, domain.Event{
			IssueID:  issueID,
			Type:     types.EventDependencyRemoved,
			Actor:    actor,
			NewValue: fmt.Sprintf("Removed dependency on %s", dependsOnID),
		}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {
			return domain.DepDeleteResult{}, fmt.Errorf("db: DependencySQLRepository.Delete: record dependency_removed event: %w", err)
		}
	}

	dt := types.DependencyType(depType)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry after checking for lock contention (another bd process or sync holding the row).
  2. Verify the database is writable (not a read-only replica).
  3. Check connectivity and re-run; Delete is safe to retry since it re-looks-up the edge.
  4. Inspect the wrapped driver error for the specific SQL failure.
Defensive patterns

Strategy: retry

Validate before calling

// confirm edge exists and DB is writable before Delete
var depType string
if err := db.QueryRow("SELECT type FROM dependencies WHERE issue_id=? AND depends_on_id=?", issueID, dependsOnID).Scan(&depType); err != nil {
    return err
}

Try / catch

res, err := repo.Delete(ctx, issueID, dependsOnID)
if err != nil {
    if strings.Contains(err.Error(), "Delete:") {
        // DELETE failed: check locks/read-only state, retry safely (lookup re-runs)
    }
}
if res != nil && !res.Found { /* edge absent */ }

Prevention

When it happens

Trigger: Calling Delete on an existing edge where ExecContext fails — foreign-key restriction from child rows, connection loss, read-only backend, or lock contention.

Common situations: Row locked by a concurrent transaction; database on a read-only replica; driver-level FK restrictions; storage quota/connection issues.

Related errors


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