gastownhall/beads · error
rename lease row: %w
Error message
rename lease row: %w
What it means
After the issue row itself was renamed, the library moves any live lease row to the new ID (`UPDATE leases SET issue_id = ?`). This error wraps a failure of that update. The whole rename transaction rolls back, so the issue keeps its old ID and its lease.
Source
Thrown at internal/storage/issueops/bulk_ops.go:314
UPDATE issues
SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
WHERE id = ?
`, newID, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, now, oldID)
if err != nil {
return fmt.Errorf("update issue ID: %w", err)
}
if rows, _ := result.RowsAffected(); rows == 0 {
return fmt.Errorf("issue not found: %s", oldID)
}
if err := UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
return err
}
// A live lease follows its issue across the rename.
if _, err := tx.ExecContext(ctx,
`UPDATE leases SET issue_id = ? WHERE issue_id = ?`, newID, oldID); err != nil {
return fmt.Errorf("rename lease row: %w", err)
}
return InsertDerivedEvent(ctx, tx, "events", AuxEvent{
IssueID: newID,
EventType: "renamed",
Actor: actor,
OldValue: str(oldID),
NewValue: str(newID),
})
}
func updateWispIDInTx(ctx context.Context, tx *sql.Tx, oldID, newID string, issue *types.Issue, actor string) error {
now := time.Now().UTC()
result, err := tx.ExecContext(ctx, `
UPDATE wisps
SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
WHERE id = ?
`, newID, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, now, oldID)View on GitHub (pinned to 71377f2769)
Solutions
- Retry the rename after the conflicting worker releases the lease (check wrapped error for lock timeout).
- Verify the leases table schema matches the current migration set.
- Check storage backend health/latency; increase lock timeout if the driver supports it.
- If a stale lease blocks the rename, release the lease through the supported API, then retry.
Example fix
// before store.UpdateIssueID(ctx, oldID, newID, issue, actor) // fails: lease row locked by worker // after store.ReleaseLease(ctx, oldID) // or wait for lease expiry err := store.UpdateIssueID(ctx, oldID, newID, issue, actor)
Defensive patterns
Strategy: retry
Validate before calling
if lease, _ := store.GetLease(ctx, oldID); lease != nil {
// consider releasing or waiting for lease expiry before renaming
} Try / catch
for attempt := 0; attempt < 3; attempt++ {
err := store.UpdateIssueID(ctx, oldID, newID, issue, actor)
if err == nil { break }
if strings.Contains(err.Error(), "rename lease row:") && isLockTimeout(err) {
time.Sleep(backoff(attempt)); continue
}
return err
} Prevention
- Don't rename issues with live leases; release or wait for expiry.
- Serialize renames against background workers that take leases.
- Monitor storage lock-timeout metrics under load.
When it happens
Trigger: Calling UpdateIssueIDInTx while the issue holds an active lease and the leases UPDATE fails — driver error, lock contention on the leases row, or schema mismatch on the leases table.
Common situations: Another worker holds/contends the lease row concurrently; connection drop mid-transaction; leases table schema changed between versions; storage backend under heavy load causing lock timeouts.
Related errors
- db: Update %s: clear lease: %w
- db: IssueSQLRepository.UnclaimIssue: %w
- db: IssueSQLRepository.HeartbeatIssue: %w
- db: IssueSQLRepository.ReclaimExpiredLeases: %w
- capture dependency edges for rename %s -> %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cd06b2ae03ff26fc.
Report an issue: GitHub.