gastownhall/beads · error
journal: resolve deleted ids in %s: %w
Error message
journal: resolve deleted ids in %s: %w
What it means
journalableDeletesInTx filters the ids to delete down to those that actually exist in the table (only existing rows get journal delete events). If ExistingIssueIDsInTableInTx fails, the error is wrapped with the table name and the delete operation aborts.
Source
Thrown at internal/storage/issueops/journal.go:343
// RecordEventInTx.
func RecordDeleteInTx(ctx context.Context, tx DBTX, issueID, actor string) error {
if !journalEnabled(ctx, tx) {
return nil
}
return insertEventRow(ctx, tx, EventDelete, issueID, nil, nil, nil, actor)
}
// journalableDeletesInTx narrows ids to the ones that actually exist in table,
// so a bulk delete records only rows it really removes. It is a no-op (nil,
// nil) when journaling is disabled, keeping the extra read off the ordinary
// local delete path. Callers MUST invoke it before issuing their DELETE.
func journalableDeletesInTx(ctx context.Context, tx DBTX, table string, ids []string) ([]string, error) {
if !journalEnabled(ctx, tx) || len(ids) == 0 {
return nil, nil
}
existing, err := ExistingIssueIDsInTableInTx(ctx, tx, table, ids)
if err != nil {
return nil, fmt.Errorf("journal: resolve deleted ids in %s: %w", table, err)
}
return existing, nil
}
// RecordDepEventInTx records a dependency add or remove for issueID, carrying
// the edge kind and target. The issue snapshot is the post-mutation state as of
// tx. A no-op when journaling is disabled. actor as on RecordEventInTx.
func RecordDepEventInTx(ctx context.Context, tx DBTX, op EventOp, issueID, kind, target, metadata, actor string) error {
if !journalEnabled(ctx, tx) {
return nil
}
issue, err := getJournalIssueInTx(ctx, tx, issueID)
if err != nil {
// The dependency source may itself have been deleted (cascade); record
// the edge change with a null snapshot rather than failing.
if errors.Is(err, storage.ErrNotFound) {
return insertEventRow(ctx, tx, op, issueID, nil, &EventDep{Kind: kind, Target: target, Metadata: metadata}, nil, actor)
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped driver error to see why the existence query failed
- Verify the table exists and matches the expected schema
- Check connectivity/permissions
- Retry the delete transaction
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
var n int
db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?", table).Scan(&n)
if n == 0 { /* migrate schema before delete-resolve flows */ } Type guard
null
Try / catch
ids, err := journalableDeletesInTx(ctx, tx, table, ids)
if err != nil {
if isTableNotExistError(err) { /* run migration, then retry */ }
return fmt.Errorf("resolve deletes failed: %w", err)
} Prevention
- Run migrations before delete/cleanup jobs
- Confirm the table name passed matches the schema
- Keep DB connectivity healthy during batch delete flows
When it happens
Trigger: DeleteResolvedSetInTx runs while the existence-check query on the given table fails: table missing, SQL error, connection loss.
Common situations: Schema drift removing a helper table; database unavailable during a resolve-set cleanup; permissions issue on the table.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- failed to remove federation peer: %w
- failed to batch delete wisps: %w
- db: LabelSQLRepository.Delete %s/%s: %w
- db: LabelSQLRepository.DeleteAllForIDs from %s: %w
- delete issue from %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c37eb39cfaad370a.
Report an issue: GitHub.