gastownhall/beads · error
delete leases: %w
Error message
delete leases: %w
What it means
After deleting the issue rows, DeleteResolvedSetInTx removes any leases referencing the deleted issues; failure of `DELETE FROM leases WHERE issue_id IN (...)` is wrapped with this message. It exists to keep lease rows from orphaning after issues disappear.
Source
Thrown at internal/storage/issueops/delete.go:330
end = len(set.RegularIDs)
}
batch := set.RegularIDs[i:end]
batchInClause, batchArgs := buildSQLInClause(batch)
deleteResult, err := tx.ExecContext(ctx,
fmt.Sprintf(`DELETE FROM issues WHERE id IN (%s)`, batchInClause),
batchArgs...)
if err != nil {
return nil, fmt.Errorf("delete issues: %w", err)
}
rowsAffected, _ := deleteResult.RowsAffected()
totalRegularsDeleted += int(rowsAffected)
// Deleted issues hold no leases.
if _, err := tx.ExecContext(ctx,
fmt.Sprintf(`DELETE FROM leases WHERE issue_id IN (%s)`, batchInClause),
batchArgs...); err != nil {
return nil, fmt.Errorf("delete leases: %w", err)
}
}
result.DeletedCount = totalRegularsDeleted + len(set.WispIDs)
// Journal every regular issue this bulk/cascade delete removed. Wisps went
// through deleteIssueRowInTx above, which journals each itself; set.All is
// cascade-expanded, so this records cascade deletes too. The delete
// plumbing carries no actor, so the rows record none.
for _, id := range journaledDeletes {
if err := RecordDeleteInTx(ctx, tx, id, ""); err != nil {
return nil, err
}
}
if err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {
return nil, fmt.Errorf("recompute is_blocked after batch delete: %w", err)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error; if it is 'no such table: leases', migrate the database schema
- Retry after the competing writer releases its lock / raise busy timeout
- Verify connection stability for remote Dolt deployments
Example fix
// before
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`DELETE FROM leases WHERE issue_id IN (%s)`, batchInClause), batchArgs...); err != nil {
return nil, fmt.Errorf("delete leases: %w", err)
}
// after — migrate first when the table is absent
// bd doctor / apply migrations before retrying the delete Defensive patterns
Strategy: try-catch
Validate before calling
// confirm the leases table exists before deleting
var n int
if err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE name='leases'").Scan(&n); err != nil || n == 0 {
return fmt.Errorf("leases table missing; run migrations")
} Try / catch
if _, err := DeleteIssuesInTx(ctx, tx, ids, opts); err != nil {
if strings.Contains(err.Error(), "delete leases: ") && strings.Contains(err.Error(), "no such table") {
migrate(db) // then retry
}
return err
} Prevention
- Run migrations before any delete operation
- Avoid holding leases on issues scheduled for deletion (release leases first)
- Use non-interactive single-writer windows for bulk deletes
When it happens
Trigger: DeleteIssuesInTx/DeleteInTx where the issues DELETE succeeded but the follow-up leases DELETE fails — leases table missing (older schema), lock contention, or connection failure mid-transaction.
Common situations: Running a new binary against an old database where the leases table does not exist; concurrent agent holding leases causing lock contention on SQLite/Dolt.
Related errors
- delete wisp %s: %w
- delete issues: %w
- recompute is_blocked after batch delete: %w
- delete wisp %s from dependencies: %w
- delete old dependency target: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f646d979eb7fbd60.
Report an issue: GitHub.