gastownhall/beads · error

db: Update %s: clear lease: %w

Error message

db: Update %s: clear lease: %w

What it means

Wrapped failure from the lease-cleanup step: after a successful UPDATE, if ManageLeaseOnUpdate determined the lease must be cleared (and the wisps table is not in use), Update runs issueops.DeleteLeaseInTx. This error means that delete failed. The issue row itself is already updated; only the lease row remains uncleared, so the unit of work aborts with an inconsistent in-transaction state (the surrounding transaction should roll back both).

Source

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

	args = append(args, id)

	//nolint:gosec // G201: table is one of two hardcoded constants
	q := fmt.Sprintf("UPDATE %s SET %s WHERE id = ?", table, strings.Join(setClauses, ", "))
	res, err := r.runner.ExecContext(ctx, q, args...)
	if err != nil {
		return fmt.Errorf("db: Update %s: %w", id, err)
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return fmt.Errorf("db: Update %s: rows affected: %w", id, err)
	}
	if rows == 0 {
		return fmt.Errorf("db: Update %s: %w", id, sql.ErrNoRows)
	}
	if clearLease && !opts.UseWispsTable {
		if err := issueops.DeleteLeaseInTx(ctx, r.runner, id); err != nil {
			return fmt.Errorf("db: Update %s: clear lease: %w", id, err)
		}
	}

	// Event-type parity: embedded records EventClosed / EventReopened /
	// EventStatusChanged for status transitions (issueops.DetermineEventType),
	// EventUpdated otherwise.
	eventType := types.EventUpdated
	if statusChanging {
		eventType = issueops.DetermineEventType(oldIssue, updates)
	}
	if err := r.events.Record(ctx, domain.Event{
		IssueID: id,
		Type:    eventType,
		Actor:   actor,
	}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {
		return err
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped inner error from DeleteLeaseInTx for the driver-level cause.
  2. Retry the whole unit of work; the transactional wrapper ensures the prior UPDATE is not partially committed.
  3. Inspect lease-row contention (concurrent Claim) and serialize conflicting operations.
  4. Verify lease table schema/permissions if the delete consistently fails.

Example fix

// before: ignoring partial failure
err := repo.Update(ctx, id, updates, actor, opts)
// after: redo the unit of work so lease cleanup re-runs
if err := repo.Update(ctx, id, updates, actor, opts); err != nil && strings.Contains(err.Error(), "clear lease") {
    return redoUnitOfWork(ctx, id, updates)
}
Defensive patterns

Strategy: retry

Validate before calling

// avoid forcing lease clear outside the wisps path unexpectedly
if clearLeaseIntended && opts.UseWispsTable { /* lease clearing is skipped — account for it */ }

Try / catch

if err := repo.Update(ctx, id, updates, actor, opts); err != nil && strings.Contains(err.Error(), "clear lease") {
    // transaction rolled back: redo the whole unit of work on a fresh session
    return redoUnitOfWork(freshCtx, id, updates)
}

Prevention

When it happens

Trigger: Calling Update whose updates clear the lease/ownership (e.g. status or assignee change per ManageLeaseOnUpdate) where DeleteLeaseInTx's DELETE fails — connectivity loss, lock contention on the lease row, or driver error inside the same transaction.

Common situations: Concurrent claim/update racing on the lease row; server hiccup mid-transaction; foreign-key or permission problems on the lease table.

Related errors


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