gastownhall/beads · error

db: IssueSQLRepository.ReclaimExpiredLeases: %w

Error message

db: IssueSQLRepository.ReclaimExpiredLeases: %w

What it means

Wraps a failure from issueops.ReclaimExpiredLeasesInTx, which finds leases older than the cutoff (optionally filtered) and reclaims them for the given actor, returning the reclaimed lease list. The wrapper only adds context; SQL, scan, or validation failures inside the helper surface here.

Source

Thrown at internal/storage/domain/db/issue.go:1272

// repository's runner (the same DBTX-shaped seam ReclaimExpiredLeases uses)
// and reports how many rows woke per table. The issues count decides whether
// the transaction's owner mints a dolt commit; the wisps count decides
// whether it must still issue a plain SQL commit — wisp tables are
// dolt_ignored, so a wisp-only wake mints no version commit, but a caller
// that treats it as "nothing happened" rolls the wisp writes back.
func (r *issueSQLRepositoryImpl) WakeExpiredDefers(ctx context.Context) (issues, wisps int, err error) {
	out, err := issueops.WakeExpiredDefersInTx(ctx, r.runner)
	if err != nil {
		return 0, 0, fmt.Errorf("db: IssueSQLRepository.WakeExpiredDefers: %w", err)
	}
	return len(out.Issues), len(out.Wisps), nil
}

func (r *issueSQLRepositoryImpl) ReclaimExpiredLeases(ctx context.Context, olderThan time.Duration, filter types.ReclaimFilter, actor string) ([]types.ReclaimedLease, error) {
	cutoff := time.Now().UTC().Add(-olderThan)
	out, err := issueops.ReclaimExpiredLeasesInTx(ctx, r.runner, cutoff, filter, actor)
	if err != nil {
		return nil, fmt.Errorf("db: IssueSQLRepository.ReclaimExpiredLeases: %w", err)
	}
	return out, nil
}

const deleteBatchSize = 200

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause; validate the ReclaimFilter fields against the current types API.
  2. Run a single reclaimer instance (or use locking) to avoid contention.
  3. Retry transient driver errors; reclamation is idempotent for already-reclaimed leases.
  4. Confirm lease/issues tables exist with current schema (migrations/doctor).

Example fix

// before
out, err := repo.ReclaimExpiredLeases(ctx, olderThan, filter, actor)
if err != nil { panic(err) }
// after
out, err := repo.ReclaimExpiredLeases(ctx, olderThan, filter, actor)
if err != nil {
    log.Printf("lease reclaim failed, retrying later: %v", err)
    return
}
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check inputs before the call
if olderThan <= 0 { return errors.New("olderThan must be positive") }
if actor == "" { return errors.New("actor required") }
if err := validateReclaimFilter(filter); err != nil { return err }

Try / catch

out, err := repo.ReclaimExpiredLeases(ctx, olderThan, filter, actor)
if err != nil {
    if isTransientDBErr(err) { return retryReclaim(err) }
    return fmt.Errorf("reclaim failed: %w", err)
}

Prevention

When it happens

Trigger: Calling ReclaimExpiredLeases(ctx, olderThan, filter, actor) when the cutoff SELECT, the reclaim UPDATEs, or event writes fail: driver errors, invalid filter values producing bad SQL, missing tables, or actor/lease validation errors in the helper.

Common situations: Background reclaimer running against a dropped connection, invalid ReclaimFilter fields after an API change, clock skew producing odd cutoffs, or concurrent reclaimers contending on the same rows.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6b62d4e895bbe210. Report an issue: GitHub.