gastownhall/beads · error
WakeExpiredDefers: %w
Error message
WakeExpiredDefers: %w
What it means
Wrapped error from WakeExpiredDefers: the repo call that flips expired DATED defers back to open failed and is prefixed with "WakeExpiredDefers: ". The method returns counts of woken permanent issues and wisps; on this error both counts are zeroed and the wake did not complete. The correct transaction form depends on whether permanent issues or only wisps woke.
Source
Thrown at internal/storage/domain/issue.go:1929
if id == "" {
return fmt.Errorf("Heartbeat: id must not be empty")
}
if err := u.issueRepo.HeartbeatIssue(ctx, id, actor); err != nil {
return fmt.Errorf("Heartbeat: %w", err)
}
return nil
}
// WakeExpiredDefers returns every expired DATED defer to open (see
// issueops.WakeExpiredDefersInTx) and reports how many permanent issues and
// wisps woke. The caller owns persistence: commit with a wake message iff
// issues > 0, and with the ephemeral plain-COMMIT form iff only wisps woke
// (wisp tables are dolt_ignored, so their wake needs a SQL commit but must
// mint no version commit).
func (u *issueUseCaseImpl) WakeExpiredDefers(ctx context.Context) (issues, wisps int, err error) {
issues, wisps, err = u.issueRepo.WakeExpiredDefers(ctx)
if err != nil {
return 0, 0, fmt.Errorf("WakeExpiredDefers: %w", err)
}
return issues, wisps, nil
}
func (u *issueUseCaseImpl) ReclaimExpiredLeases(ctx context.Context, olderThan time.Duration, filter types.ReclaimFilter, actor string) ([]types.ReclaimedLease, error) {
out, err := u.issueRepo.ReclaimExpiredLeases(ctx, olderThan, filter, actor)
if err != nil {
return nil, fmt.Errorf("ReclaimExpiredLeases: %w", err)
}
return out, nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Unwrap the error to find the underlying repository/transaction cause
- Check database connectivity and retry the wake sweep
- Ensure the sweep runs with the correct tx form: version-commit when issues > 0, plain ephemeral commit when only wisps woke
- Look for lock contention from concurrent bd processes and serialize the sweep
Example fix
// before
i, w, err := uc.WakeExpiredDefers(ctx)
if err != nil { return err }
// after
i, w, err := uc.WakeExpiredDefers(ctx)
if err != nil {
log.Printf("defer wake failed, will retry: %v", err)
return retryWithBackoff(func() error { _, _, err = uc.WakeExpiredDefers(ctx); return err })
} Defensive patterns
Strategy: retry
Try / catch
issues, wisps, err := uc.WakeExpiredDefers(ctx)
if err != nil {
return retryWithBackoff(func() error {
issues, wisps, err = uc.WakeExpiredDefers(ctx)
return err
})
} Prevention
- Schedule the wake sweep with a single owner (lock or leader election) to avoid conflicts
- Run DB health checks before each sweep
- Log the wrapped cause to distinguish tx-form problems from outages
- Retry with backoff — the sweep is idempotent for remaining expired defers
When it happens
Trigger: Calling WakeExpiredDefers(ctx) when issueRepo.WakeExpiredDefers fails: database unavailable, transaction/commit error (e.g. wrong tx form when permanent issues are involved and a version commit is required), or SQL error in the wake scan.
Common situations: Periodic defer-wakeup jobs (cron/daemon) hitting a DB outage; concurrent writers conflicting with the wake transaction; misconfigured ephemeral-vs-versioned commit mode.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6f3b300420e4d0b9.
Report an issue: GitHub.