gastownhall/beads · error
journal dependency removals for batch delete: %w
Error message
journal dependency removals for batch delete: %w
What it means
DeleteResolvedSetInTx aborts when RecordDependencyRemovalsForIssuesInTx fails to journal the dependency edges about to be removed. Journaling happens before the DELETE rows run so consumers can be told which edges vanished; if journaling fails, the delete is aborted rather than losing edge provenance. This is deliberately ordered before the row deletes.
Source
Thrown at internal/storage/issueops/delete.go:299
}
affectedIssues, affectedWisps, aerr := AffectedByDeletionInTx(ctx, tx, set.RegularIDs, set.WispIDs)
if aerr != nil {
return nil, fmt.Errorf("affected by batch delete: %w", aerr)
}
// Resolve WHICH regular ids this delete actually removes before the batched
// DELETE runs: afterwards the rows are gone, and RowsAffected reports a
// count, not a set. A journal record for an id that was already absent would
// tell a consumer to drop a bead this transaction never touched.
journaledDeletes, err := journalableDeletesInTx(ctx, tx, "issues", set.RegularIDs)
if err != nil {
return nil, err
}
// Edges are journaled before the rows go, while their source snapshots can
// still be read.
if err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, set.All); err != nil {
return nil, fmt.Errorf("journal dependency removals for batch delete: %w", err)
}
for _, id := range set.WispIDs {
if err := deleteIssueRowInTx(ctx, tx, id, true); err != nil {
return nil, fmt.Errorf("delete wisp %s: %w", id, err)
}
}
totalRegularsDeleted := 0
for i := 0; i < len(set.RegularIDs); i += deleteBatchSize {
end := i + deleteBatchSize
if end > len(set.RegularIDs) {
end = len(set.RegularIDs)
}
batch := set.RegularIDs[i:end]
batchInClause, batchArgs := buildSQLInClause(batch)
deleteResult, err := tx.ExecContext(ctx,View on GitHub (pinned to 71377f2769)
Solutions
- Unwrap the error to find the insert failure (constraint, permissions, disk).
- Check disk space and write permissions on the database.
- Clear/inspect stale journal rows if unique-constraint conflicts occur on retries.
- Retry the delete in a fresh transaction — the abort is designed to leave data untouched.
Example fix
// before
res, err := store.DeleteIssuesInTx(ctx, tx, ids)
if err != nil {
log.Fatal(err)
}
// after
res, err := store.DeleteIssuesInTx(ctx, tx, ids)
if err != nil {
log.Printf("delete aborted before mutation (journal failure): %v", err)
return err
} Defensive patterns
Strategy: fallback
Validate before calling
// ensure the journal table is writable and has space
if err := db.PingContext(ctx); err != nil {
return fmt.Errorf("db unwritable before delete: %w", err)
}
if err := checkDiskSpace(dataDir, 100<<20); err != nil {
return fmt.Errorf("insufficient disk for delete journal: %w", err)
} Try / catch
res, err := store.DeleteIssuesInTx(ctx, tx, ids)
if err != nil && strings.Contains(err.Error(), "journal dependency removals") {
return fmt.Errorf("delete aborted before mutation (safe to fix and retry): %w", err)
} Prevention
- Monitor disk space on embedded databases.
- Avoid retry loops that re-enter a partially failed delete without inspecting journal state.
- Run deletes with write access, never against read-only replicas.
When it happens
Trigger: Calling DeleteIssuesInTx/DeleteInTx when the journal insert fails: journal table missing/full/corrupt, connection drop, unique-constraint conflicts on re-journaled edges, or context cancellation during the insert batch.
Common situations: Read-only database or insufficient write permissions on the journal table; disk full on embedded databases; a retry re-entering the delete after a partial failure causing journal key collisions; driver without journal table support.
Related errors
- count wisp dependencies: %w
- count labels: %w
- count events: %w
- affected by batch delete: %w
- failed to create digest issue: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/777afcfb46b3adde.
Report an issue: GitHub.