{"record":{"id":"2229b5dc111e004c","repo":"gastownhall/beads","slug":"failed-to-claim-issue-w","errorCode":null,"errorMessage":"failed to claim issue: %w","messagePattern":"failed to claim issue: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/claim.go","lineNumber":130,"sourceCode":"\t\t\targs = append(args, id, oldIssue.RowVersion)\n\t\t\targs = append(args, statusArgs...)\n\t\t\tresult, err = tx.ExecContext(ctx, fmt.Sprintf(`\n\t\t\t\tUPDATE %s\n\t\t\t\tSET assignee = ?, status = 'in_progress', updated_at = ?, started_at = ?, %s\n\t\t\t\tWHERE id = ? AND row_lock = ? AND status IN (%s)\n\t\t\t`, issueTable, rowLockClause, statusPlaceholders), args...)\n\t\t} else {\n\t\t\targs := append([]interface{}{actor, now}, rowLockArgs...)\n\t\t\targs = append(args, id, oldIssue.RowVersion)\n\t\t\targs = append(args, statusArgs...)\n\t\t\tresult, err = tx.ExecContext(ctx, fmt.Sprintf(`\n\t\t\t\tUPDATE %s\n\t\t\t\tSET assignee = ?, status = 'in_progress', updated_at = ?, %s\n\t\t\t\tWHERE id = ? AND row_lock = ? AND status IN (%s)\n\t\t\t`, issueTable, rowLockClause, statusPlaceholders), args...)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to claim issue: %w\", err)\n\t\t}\n\t\trowsAffected, err = result.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get rows affected: %w\", err)\n\t\t}\n\t}\n\n\tif rowsAffected == 0 {\n\t\tassignee, currentStatus, err := readClaimStateInTx(ctx, tx, issueTable, id)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get current claim state: %w\", err)\n\t\t}\n\t\t// Idempotent: if already claimed in_progress by the same actor —\n\t\t// including a spelling difference across layers (ga-wzl83) — treat as\n\t\t// success. This supports agent retry workflows where claim may be\n\t\t// called multiple times after transient failures (GH#8).\n\t\tif actorMatches(assignee, actor) && currentStatus == types.StatusInProgress {\n\t\t\treturn &ClaimResult{OldIssue: oldIssue, IsWisp: isWisp}, nil","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/claim.go#L112-L148","documentation":"ClaimIssueInTx wraps any error returned by the database driver when executing the claim compare-and-swap UPDATE (setting assignee/status='in_progress' guarded on row_lock and claimable status). This means the SQL execution itself failed — not that the claim was refused — so the claim did not happen and the wrapped driver error carries the root cause (connection loss, syntax/constraint error, context cancellation).","triggerScenarios":"Calling ClaimIssueInTx (via bd claim, ClaimReadyIssueInTx, ExecuteUpdate, ExecuteClaim) when the backing Dolt/SQL connection errors on the UPDATE: dropped connection, canceled context, constraint violation, or a backend engine error during the CAS write.","commonSituations":"Network blip between the CLI and the Dolt server mid-claim; the transaction's context deadline exceeded on a slow/loaded server; a driver/schema mismatch after upgrading bd while the DB is an older version.","solutions":["Read the wrapped driver error (%w) for the root cause and fix it (reconnect, restart server, etc.)","Retry the claim — the CAS is idempotent for the same actor once in_progress","Check DB connectivity/schema version (bd doctor) if the failure is persistent"],"exampleFix":"// before\nissued, err := ClaimIssueInTx(ctx, tx, id, actor) // raw failure surfaces here\n// after\nissued, err := ClaimIssueInTx(ctx, tx, id, actor)\nif err != nil {\n    var conflict *publicops.ClaimConflictError\n    if !errors.As(err, &conflict) { // only transient/infra failures fall through\n        time.Sleep(backoff); return ClaimIssueInTx(ctx, tx, id, actor)\n    }\n    return nil, err\n}","handlingStrategy":"retry","validationCode":"// ensure connectivity before claiming\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unavailable: %w\", err) }","typeGuard":null,"tryCatchPattern":"res, err := ClaimIssueInTx(ctx, tx, id, actor)\nif err != nil {\n    var conflict *publicops.ClaimConflictError\n    if errors.As(err, &conflict) { return handleConflict(conflict) }\n    if errors.Is(ctx.Err(), context.Canceled) { return ctx.Err() }\n    return retryWithBackoff(func() error { _, err := ClaimIssueInTx(ctx, tx, id, actor); return err })\n}","preventionTips":["Keep the DB connection healthy (timeouts, pings) before claim bursts","Distinguish claim conflicts (typed ClaimConflictError) from infra errors before retrying","Use bounded retries with backoff — the CAS is idempotent for the same actor"],"tags":["database","sql","storage","claim"],"backgroundTag":"database-update-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}