gastownhall/beads · error
db: IssueSQLRepository.UnclaimIssue: %w
Error message
db: IssueSQLRepository.UnclaimIssue: %w
What it means
Wraps a failure from issueops.UnclaimIssueInTx when releasing an issue/wisp claim unconditionally. The helper routes to issues or wisps tables automatically; failures can be lookup errors, SQL exec errors, or 'not claimed' style validation errors from finishUnclaimInTx. The wrapper adds repository context only.
Source
Thrown at internal/storage/domain/db/issue.go:1216
func (r *issueSQLRepositoryImpl) GetStaleIssues(ctx context.Context, filter types.StaleFilter) ([]*types.Issue, error) {
out, err := issueops.GetStaleIssuesInTx(ctx, r.runner, filter)
if err != nil {
return nil, fmt.Errorf("db: IssueSQLRepository.GetStaleIssues: %w", err)
}
return out, nil
}
func (r *issueSQLRepositoryImpl) GetEpicsEligibleForClosure(ctx context.Context) ([]*types.EpicStatus, error) {
out, err := issueops.GetEpicsEligibleForClosureInTx(ctx, r.runner)
if err != nil {
return nil, fmt.Errorf("db: IssueSQLRepository.GetEpicsEligibleForClosure: %w", err)
}
return out, nil
}
func (r *issueSQLRepositoryImpl) UnclaimIssue(ctx context.Context, id, actor string, force bool) error {
if err := issueops.UnclaimIssueInTx(ctx, r.runner, id, actor, force); err != nil {
return fmt.Errorf("db: IssueSQLRepository.UnclaimIssue: %w", err)
}
return nil
}
// UnclaimIssueIfAssignee runs the classic compare-and-swap release against this
// runner. Like UnclaimIssue it takes no IssueTableOpts: issueops routes the
// write to the issues or wisps tables from the row itself, so a wisp's claim is
// released against the wisp tables on both backends. The mismatch verdict
// (storage.ErrAssigneeMismatch, nothing written) is produced by the shared
// helper, not restated here.
func (r *issueSQLRepositoryImpl) UnclaimIssueIfAssignee(ctx context.Context, id, actor, expectedAssignee string) error {
if err := issueops.UnclaimIssueIfAssigneeInTx(ctx, r.runner, id, actor, expectedAssignee); err != nil {
return fmt.Errorf("db: IssueSQLRepository.UnclaimIssueIfAssignee: %w", err)
}
return nil
}
// HeartbeatIssue refreshes the lease on an issue actor holds in_progress,View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped cause to distinguish lookup/exec failures from validation failures.
- Check the issue still exists and has an active claim before releasing (or handle idempotently).
- Retry on transient driver errors; the transaction rolls back cleanly.
- Verify the lease/claim tables exist and the schema is current.
Example fix
// before
if err := repo.UnclaimIssue(ctx, id, actor, false); err != nil { return err }
// after
if err := repo.UnclaimIssue(ctx, id, actor, false); err != nil {
if strings.Contains(err.Error(), "not claimed") { return nil } // already released
return fmt.Errorf("unclaim %s: %w", id, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// confirm the claim exists before releasing
issue, err := repo.Get(ctx, id)
if err != nil || issue.Assignee == "" { return nil } // nothing to unclaim Try / catch
if err := repo.UnclaimIssue(ctx, id, actor, false); err != nil {
if isAlreadyReleased(err) { return nil } // idempotent no-op
return fmt.Errorf("unclaim %s: %w", id, err)
} Prevention
- Make unclaim idempotent in callers (already-released is success).
- Pass the same actor string used at Claim time.
- Check issue existence before mutation.
- Use force only when you intend to override staleness checks.
When it happens
Trigger: Calling UnclaimIssue(ctx, id, actor, force) when: the issue lookup fails (GetIssueInTx error), the UPDATE/DELETE of the lease row fails at the driver level, the ID is empty/invalid, or finishing the unclaim (event write, rows-affected check) errors.
Common situations: Concurrent modification (claim already released) surfacing as an unexpected rows-affected result, connection loss mid-transaction, or targeting an ID that was deleted between check and write.
Related errors
- db: Update %s: clear lease: %w
- db: Claim %s: read old issue: %w
- db: Claim %s: resolve claim pools: %w
- db: Claim %s: resolve claimable statuses: %w
- db: Claim %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4e750d42cf900586.
Report an issue: GitHub.