{"record":{"id":"f4883c134430caaf","repo":"gastownhall/beads","slug":"db-claim-s-w","errorCode":null,"errorMessage":"db: Claim %s: %w","messagePattern":"db: Claim (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue.go","lineNumber":485,"sourceCode":"\t\t\t//nolint:gosec // G201: table is one of two hardcoded constants\n\t\t\tres, err = r.runner.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 (%s)\n\t\t\t`, table, rowLockClause, statusPredicate), args...)\n\t\t} else {\n\t\t\targs := append([]any{actor, now}, rowLockArgs...)\n\t\t\targs = append(args, id, oldIssue.RowVersion)\n\t\t\targs = append(args, statusArgs...)\n\t\t\t//nolint:gosec // G201: table is one of two hardcoded constants\n\t\t\tres, err = r.runner.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 (%s)\n\t\t\t`, table, rowLockClause, statusPredicate), args...)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn domain.ClaimRowResult{}, fmt.Errorf(\"db: Claim %s: %w\", id, err)\n\t\t}\n\t\trows, err = res.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn domain.ClaimRowResult{}, fmt.Errorf(\"db: Claim %s: rows affected: %w\", id, err)\n\t\t}\n\t}\n\n\tif rows == 0 {\n\t\tvar currentAssignee sql.NullString\n\t\tvar currentStatus types.Status\n\t\t//nolint:gosec // G201: table is one of two hardcoded constants\n\t\tif err := r.runner.QueryRowContext(ctx,\n\t\t\tfmt.Sprintf(\"SELECT assignee, status FROM %s WHERE id = ?\", table), id,\n\t\t).Scan(&currentAssignee, &currentStatus); err != nil {\n\t\t\treturn domain.ClaimRowResult{}, fmt.Errorf(\"db: Claim %s: read current state: %w\", id, err)\n\t\t}\n\t\tassignee := \"\"\n\t\tif currentAssignee.Valid {","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue.go#L467-L503","documentation":"This error wraps a failure of the claim UPDATE statement itself — a conditional CAS update (UPDATE ... SET assignee=?, status='in_progress' WHERE id=? AND row_lock=? AND (<status predicate>)). Unlike a zero-row match (handled separately), this is an execution error of the statement, which aborts Claim.","triggerScenarios":"Calling Claim when the UPDATE execution fails at the driver/SQL level: connection loss, deadlock/lock-wait timeout on the issue row, malformed generated status predicate, or context cancellation mid-statement.","commonSituations":"Heavy contention on the same issue row causing lock waits; database failover mid-claim; SQL errors from malformed custom status configuration; stale pooled connections.","solutions":["Inspect the wrapped cause (%w): deadlock/timeout suggests retrying the claim.","Retry the claim — it is a CAS and safe to re-attempt after transient failures.","Check for another process holding a lease/lock on the issue row; wait or clear it.","Validate custom status configuration so the generated status predicate is well-formed."],"exampleFix":"// before\nres, err := store.Claim(ctx, id, actor, opts) // fails on lock timeout\n// after\nfor i := 0; i < 3; i++ {\n    res, err = store.Claim(ctx, id, actor, opts)\n    if err == nil || !isTransient(err) { break }\n    time.Sleep(100 * time.Millisecond << i)\n}","handlingStrategy":"retry","validationCode":"issue, _ := store.Get(ctx, id, opts)\nif issue.Assignee != \"\" && issue.Assignee != actor {\n    return fmt.Errorf(\"issue %s already assigned to %s\", id, issue.Assignee)\n}","typeGuard":null,"tryCatchPattern":"res, err := store.Claim(ctx, id, actor, opts)\nif err != nil && isDeadlockOrTimeout(err) {\n    time.Sleep(200 * time.Millisecond)\n    res, err = store.Claim(ctx, id, actor, opts) // CAS is safe to retry\n}","preventionTips":["Use exponential backoff for claim retries under contention.","Check current assignee before claiming to reduce lost races.","Respect existing leases; don't fight another active agent's claim.","Serialize claims per issue within your own tooling."],"tags":["database","claim","cas-update"],"backgroundTag":"db-deadlock","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}