gastownhall/beads · critical
delete issues: %w
Error message
delete issues: %w
What it means
The core bulk DELETE (`DELETE FROM issues WHERE source_repo = ?`) failing is wrapped as "delete issues: %w". This is the main destructive statement; its failure rolls back nothing by itself (caller's tx governs), but signals the issues rows were not removed.
Source
Thrown at internal/storage/issueops/bulk_ops.go:215
return 0, fmt.Errorf("affected by source-repo delete: %w", aerr)
}
// Deleted issues hold no leases: clear them while the id set is still
// joinable (before the issues rows go away).
if _, err := tx.ExecContext(ctx,
`DELETE FROM leases WHERE issue_id IN (SELECT id FROM issues WHERE source_repo = ?)`, sourceRepo); err != nil {
return 0, fmt.Errorf("delete leases: %w", err)
}
// Edges are journaled before the rows go, while their source snapshots can
// still be read.
if err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, issueIDs); err != nil {
return 0, fmt.Errorf("journal dependency removals for source-repo delete: %w", err)
}
result, err := tx.ExecContext(ctx, `DELETE FROM issues WHERE source_repo = ?`, sourceRepo)
if err != nil {
return 0, fmt.Errorf("delete issues: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, fmt.Errorf("rows affected: %w", err)
}
// Journal each deleted issue in the same transaction. issueIDs is the exact
// set removed by the DELETE above (both were scoped to source_repo), so
// there are no phantom records here. The source-repo bulk delete plumbing
// carries no actor, so the rows record none.
for _, id := range issueIDs {
if err := RecordDeleteInTx(ctx, tx, id, ""); err != nil {
return int(rowsAffected), err
}
}
if err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped error — a FOREIGN KEY constraint failed means a child table still references the issues
- Migrate or add CASCADE handling for any new referencing tables
- Retry when the database lock clears; serialize bulk deletes against other writers
- Point the operation at a writable primary connection, not a read-only replica
Defensive patterns
Strategy: try-catch
Validate before calling
// detect tables referencing issues without cascade before deleting
rows, _ := db.Query("SELECT name FROM sqlite_master WHERE type='table'") // inspect schema for FK references Type guard
if strings.Contains(err.Error(), "FOREIGN KEY constraint failed") { /* a child table still references issues */ } Try / catch
err := DeleteIssuesBySourceRepoInTx(ctx, tx, repo)
if err != nil {
if strings.Contains(err.Error(), "FOREIGN KEY") {
// migrate schema to add ON DELETE CASCADE for the referencing table
}
// the tx is aborted: rollback and retry after fixing
tx.Rollback()
return err
} Prevention
- Roll back the transaction on any bulk-delete error; steps are atomic in intent
- Extend migrations (not runtime patches) whenever new tables reference issues
- Run bulk deletes against a writable primary, never a read-only replica
When it happens
Trigger: tx.ExecContext on the DELETE fails: foreign-key constraint from a table still referencing issues (no ON DELETE CASCADE), database locked by a concurrent writer, read-only connection, or context canceled.
Common situations: Schema variant lacking CASCADE on dependent tables (e.g. a plugin-added table referencing issues); another bd process mid-write; replica/read-only connection mistakenly used for the delete.
Related errors
- query issues: %w
- affected by source-repo delete: %w
- delete leases: %w
- get dependents: %w
- count dependencies: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/004e448ca03e2928.
Report an issue: GitHub.