gastownhall/beads · error

db: Claim %s: resolve claimable statuses: %w

Error message

db: Claim %s: resolve claimable statuses: %w

What it means

This error wraps a failure of ClaimableSourceStatusesInTx, which resolves the statuses an issue may be claimed from ("open" plus custom active-category statuses) inside the Claim transaction. It prevents hardcoding status='open'; if the lookup fails, Claim aborts with this wrapped error.

Source

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

	if err != nil {
		return domain.ClaimRowResult{}, fmt.Errorf("db: Claim %s: resolve claim pools: %w", id, err)
	}

	// Claimability of the assignee slot, judged in Go against oldIssue rather
	// than as a spelling-sensitive SQL predicate (ga-v2k49, mirroring the
	// same-day fix to issueops.ClaimIssueInTx — this dual must stay in
	// lockstep, per the comment above): empty/unassigned, already this actor
	// — including a spelling difference across layers (ga-wzl83) — or a
	// claim-pool alias. issueops.ActorMatches is the exported form of the
	// primary path's package-local actorMatches, kept for exactly this dual.
	assigneeOK := oldIssue.Assignee == "" || issueops.ActorMatches(oldIssue.Assignee, actor) || slices.Contains(pools, oldIssue.Assignee)

	// Same lockstep for the source statuses (bd-pq7m2): claimable from "open"
	// plus custom active-category statuses, like the primary path — not a
	// hardcoded status = 'open'.
	claimableStatuses, err := issueops.ClaimableSourceStatusesInTx(ctx, r.runner)
	if err != nil {
		return domain.ClaimRowResult{}, fmt.Errorf("db: Claim %s: resolve claimable statuses: %w", id, err)
	}
	statusPredicate := "status = ?"
	statusArgs := []any{claimableStatuses[0]}
	for _, st := range claimableStatuses[1:] {
		statusPredicate += " OR status = ?"
		statusArgs = append(statusArgs, st)
	}

	// Conditional UPDATE, attempted only while assigneeOK — otherwise there
	// is nothing this actor could win, so skip straight to the rows==0
	// disambiguation below (matches the primary path's ga-v2k49 fix). CASed
	// on row_lock rather than re-checking assignee in SQL: row_lock is
	// rewritten by every path that mutates status/assignee/started_at (see
	// the freshRowLock invariant in issueops/lease.go), so requiring it to
	// still equal oldIssue.RowVersion detects a race exactly as precisely as
	// the old assignee predicate did, without embedding a spelling-sensitive
	// string comparison in SQL.
	var rows int64

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (%w) for the underlying SQL/driver error.
  2. Verify custom status definitions exist and the statuses storage is intact.
  3. Re-run migrations to restore expected status configuration schema.
  4. Retry once the database is reachable.

Example fix

// before
// statuses config manually emptied; Claim fails resolving statuses
// after
bd doctor  // verify DB integrity, restore statuses config, retry claim
Defensive patterns

Strategy: validation

Validate before calling

// confirm the issue is in a claimable status before attempting
issue, _ := store.Get(ctx, id, opts)
if issue.Status != "open" && issue.Status.Category() != types.CategoryInProgress {
    return fmt.Errorf("issue %s not claimable (status=%s)", id, issue.Status)
}

Type guard

func claimableStatus(st types.Status) bool {
    return st == "open" || st.Category() == types.CategoryActive
}

Try / catch

_, err := store.Claim(ctx, id, actor, opts)
if err != nil && strings.Contains(err.Error(), "resolve claimable statuses") {
    return fmt.Errorf("status config unreadable; run migrations/bd doctor: %w", err)
}

Prevention

When it happens

Trigger: Calling Claim when the SQL resolving claimable source statuses fails: connection error, aborted transaction, missing status configuration storage, context cancellation.

Common situations: Custom status definitions removed or corrupted after manual DB edits; schema drift between versions; transient database outages during a claim.

Related errors


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