{"record":{"id":"07b9614ea08209ff","repo":"gastownhall/beads","slug":"db-claim-s-resolve-claimable-statuses-w","errorCode":null,"errorMessage":"db: Claim %s: resolve claimable statuses: %w","messagePattern":"db: Claim (.+?): resolve claimable statuses: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue.go","lineNumber":442,"sourceCode":"\tif err != nil {\n\t\treturn domain.ClaimRowResult{}, fmt.Errorf(\"db: Claim %s: resolve claim pools: %w\", id, err)\n\t}\n\n\t// Claimability of the assignee slot, judged in Go against oldIssue rather\n\t// than as a spelling-sensitive SQL predicate (ga-v2k49, mirroring the\n\t// same-day fix to issueops.ClaimIssueInTx — this dual must stay in\n\t// lockstep, per the comment above): empty/unassigned, already this actor\n\t// — including a spelling difference across layers (ga-wzl83) — or a\n\t// claim-pool alias. issueops.ActorMatches is the exported form of the\n\t// primary path's package-local actorMatches, kept for exactly this dual.\n\tassigneeOK := oldIssue.Assignee == \"\" || issueops.ActorMatches(oldIssue.Assignee, actor) || slices.Contains(pools, oldIssue.Assignee)\n\n\t// Same lockstep for the source statuses (bd-pq7m2): claimable from \"open\"\n\t// plus custom active-category statuses, like the primary path — not a\n\t// hardcoded status = 'open'.\n\tclaimableStatuses, err := issueops.ClaimableSourceStatusesInTx(ctx, r.runner)\n\tif err != nil {\n\t\treturn domain.ClaimRowResult{}, fmt.Errorf(\"db: Claim %s: resolve claimable statuses: %w\", id, err)\n\t}\n\tstatusPredicate := \"status = ?\"\n\tstatusArgs := []any{claimableStatuses[0]}\n\tfor _, st := range claimableStatuses[1:] {\n\t\tstatusPredicate += \" OR status = ?\"\n\t\tstatusArgs = append(statusArgs, st)\n\t}\n\n\t// Conditional UPDATE, attempted only while assigneeOK — otherwise there\n\t// is nothing this actor could win, so skip straight to the rows==0\n\t// disambiguation below (matches the primary path's ga-v2k49 fix). CASed\n\t// on row_lock rather than re-checking assignee in SQL: row_lock is\n\t// rewritten by every path that mutates status/assignee/started_at (see\n\t// the freshRowLock invariant in issueops/lease.go), so requiring it to\n\t// still equal oldIssue.RowVersion detects a race exactly as precisely as\n\t// the old assignee predicate did, without embedding a spelling-sensitive\n\t// string comparison in SQL.\n\tvar rows int64","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue.go#L424-L460","documentation":"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.","triggerScenarios":"Calling Claim when the SQL resolving claimable source statuses fails: connection error, aborted transaction, missing status configuration storage, context cancellation.","commonSituations":"Custom status definitions removed or corrupted after manual DB edits; schema drift between versions; transient database outages during a claim.","solutions":["Inspect the wrapped cause (%w) for the underlying SQL/driver error.","Verify custom status definitions exist and the statuses storage is intact.","Re-run migrations to restore expected status configuration schema.","Retry once the database is reachable."],"exampleFix":"// before\n// statuses config manually emptied; Claim fails resolving statuses\n// after\nbd doctor  // verify DB integrity, restore statuses config, retry claim","handlingStrategy":"validation","validationCode":"// confirm the issue is in a claimable status before attempting\nissue, _ := store.Get(ctx, id, opts)\nif issue.Status != \"open\" && issue.Status.Category() != types.CategoryInProgress {\n    return fmt.Errorf(\"issue %s not claimable (status=%s)\", id, issue.Status)\n}","typeGuard":"func claimableStatus(st types.Status) bool {\n    return st == \"open\" || st.Category() == types.CategoryActive\n}","tryCatchPattern":"_, err := store.Claim(ctx, id, actor, opts)\nif err != nil && strings.Contains(err.Error(), \"resolve claimable statuses\") {\n    return fmt.Errorf(\"status config unreadable; run migrations/bd doctor: %w\", err)\n}","preventionTips":["Do not delete custom status definitions without migrating dependent issues.","Re-run migrations after version upgrades.","Check status configuration with bd doctor before bulk claims.","Pre-check issue status in Go to avoid avoidable claim attempts."],"tags":["database","claim","statuses"],"backgroundTag":"claim-status-resolution-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}