gastownhall/beads · error
failed to get issue for claim: %w
Error message
failed to get issue for claim: %w
What it means
ClaimIssueInTx reads the pre-image of the issue inside the transaction (via GetIssueInTx) before applying the claim CAS; failure is wrapped as "failed to get issue for claim: %w". The pre-image is required both for event recording and for assignee-claimability checks, so a claim cannot proceed without it.
Source
Thrown at internal/storage/issueops/claim.go:49
// success (supports agent retry workflows).
// Routes to the correct table (issues/wisps) automatically.
// The caller is responsible for Dolt versioning (DOLT_ADD/COMMIT) if needed.
//
//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)
func ClaimIssueInTx(ctx context.Context, tx DBTX, id string, actor string) (*ClaimResult, error) {
// The CAS below writes assignee = actor. actor is user-settable (--actor /
// BEADS_ACTOR), so bound it against the VARCHAR(255) assignee column up front
// and return a typed ErrFieldTooLong rather than a raw backend error.
if err := types.CheckFieldLen("actor", actor); err != nil {
return nil, err
}
isWisp := IsActiveWispInTx(ctx, tx, id)
issueTable, _, eventTable, _ := WispTableRouting(isWisp)
// Read old issue inside the transaction for event recording.
oldIssue, err := GetIssueInTx(ctx, tx, id)
if err != nil {
return nil, fmt.Errorf("failed to get issue for claim: %w", err)
}
now := time.Now().UTC()
// Rewrite row_lock with the claim (see lease.go): a concurrent reclaim or
// close on the same row is forced to conflict rather than silently
// cell-merge. The lease itself is granted separately below, in the
// ephemeral leases table — claims commit (status/assignee are
// history-worthy) but lease grants and heartbeats do not (bd-lrgn1).
rowLockClause, rowLockArgs := RowLockClause()
// An issue is claimable from "open" plus any configured custom status whose
// category is "active" (e.g. a draft->ready->in_progress lifecycle where
// "ready" should be claimable). WIP/done/frozen customs are excluded so the
// anti-steal protection from GH-3570 is preserved.
claimableStatuses, err := ClaimableSourceStatusesInTx(ctx, tx)
if err != nil {
return nil, fmt.Errorf("failed to resolve claimable statuses: %w", err)View on GitHub (pinned to 71377f2769)
Solutions
- Verify the issue exists first: bd show <id>; if gone, the claim target is stale.
- Check the wrapped error: 'no rows' means wrong or deleted ID; connection errors mean retry the transaction.
- Re-sync (bd sync) if the ID comes from another machine's export and may have been compacted or deleted.
- Ensure you pass the full ID (prefix + number) exactly as stored.
Example fix
// before
iss, err := store.ClaimIssue(ctx, id, actor)
if err != nil { return err }
// after
iss, err := store.ClaimIssue(ctx, id, actor)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
return fmt.Errorf("issue %s not found; run bd sync", id)
}
return err
} Defensive patterns
Strategy: validation
Validate before calling
iss, err := store.GetIssue(ctx, id)
if err != nil {
return fmt.Errorf("cannot claim %s: issue not found; run bd sync", id)
} Type guard
func issueExists(iss *types.Issue, err error) bool {
return err == nil && iss != nil && iss.ID != ""
} Try / catch
iss, err := store.ClaimIssue(ctx, id, actor)
if err != nil {
if strings.Contains(err.Error(), "no rows") || strings.Contains(err.Error(), "not found") {
return fmt.Errorf("issue %s missing locally; run bd sync", id)
}
return err
} Prevention
- Run bd sync before scripted claims so local IDs are current.
- Validate issue IDs (bd show) before claim loops.
- Handle deletions: another agent may have closed/deleted the issue.
- Use bd ready to pick claimable IDs instead of caching stale ID lists.
When it happens
Trigger: Calling ClaimIssueInTx / ExecuteClaim / ExecuteUpdate when: the issue ID does not exist (ErrNoRows from GetIssueInTx), the row routing (wisp vs issue table) mismatched so the lookup hit the wrong table, or the SELECT failed from connection/context errors.
Common situations: Claiming an issue that was already deleted in another session; stale ID from a jsonl export; claiming a wisp ID through the non-wisp path after a compaction moved it; typos in the issue ID.
Related errors
- 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
- db: Claim %s: read current state: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/784318a0a896dab1.
Report an issue: GitHub.