{"record":{"id":"5d9873cdd827afdc","repo":"gastownhall/beads","slug":"failed-to-read-assignee-status-for-s-w","errorCode":null,"errorMessage":"failed to read assignee/status for %s: %w","messagePattern":"failed to read assignee/status for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/update_cas.go","lineNumber":50,"sourceCode":"//\n//nolint:gosec // G201: table name comes from WispTableRouting (hardcoded constants)\nfunc CheckExpectedFieldsInTx(ctx context.Context, tx DBTX, id string, expectedAssignee, expectedStatus *string) error {\n\tif expectedAssignee == nil && expectedStatus == nil {\n\t\treturn nil\n\t}\n\tisWisp := IsActiveWispInTx(ctx, tx, id)\n\tissueTable, _, _, _ := WispTableRouting(isWisp)\n\n\tvar assignee sql.NullString\n\tvar status string\n\terr := tx.QueryRowContext(ctx,\n\t\tfmt.Sprintf(\"SELECT assignee, status FROM %s WHERE id = ?\", issueTable), id,\n\t).Scan(&assignee, &status)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, id)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read assignee/status for %s: %w\", id, err)\n\t}\n\tif expectedAssignee != nil && !actorMatches(assignee.String, *expectedAssignee) {\n\t\treturn fmt.Errorf(\"%w: %s is held by %q, expected %q\", storage.ErrAssigneeMismatch, id, assignee.String, *expectedAssignee)\n\t}\n\tif expectedStatus != nil && status != *expectedStatus {\n\t\treturn fmt.Errorf(\"%w: %s has status %q, expected %q\", storage.ErrStatusMismatch, id, status, *expectedStatus)\n\t}\n\treturn nil\n}\n","sourceCodeStart":32,"sourceCodeEnd":60,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/update_cas.go#L32-L60","documentation":"CheckExpectedFieldsInTx wraps any non-no-rows database error from reading assignee/status with this message. It means the precondition check itself could not run — not that a field mismatched — so the CAS update is aborted rather than silently proceeding unguarded.","triggerScenarios":"ExecuteUpdate with ExpectedFields while the underlying SELECT assignee,status FROM issues WHERE id=? fails due to connection loss, lock timeouts, permission denial, or schema drift (missing columns).","commonSituations":"DB restarted or network blip mid-transaction; migrations removed/renamed assignee or status columns; insufficient DB user grants; Dolt server overload causing query timeouts.","solutions":["Inspect the wrapped %w cause to identify the driver-level failure","Retry the operation if the error is transient (connection reset, lock wait timeout)","Run schema checks/migrations to confirm assignee and status columns exist","Verify DB credentials and grants for the issuing process"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify schema/connectivity first\nrow := db.QueryRow(\"SELECT assignee, status FROM issues WHERE id = ?\", id)\nif err := row.Err(); err != nil { return err }","typeGuard":null,"tryCatchPattern":"err := ExecuteUpdate(ctx, tx, id, updates, exp)\nif err != nil && !errors.Is(err, storage.ErrNotFound) &&\n   !errors.Is(err, storage.ErrAssigneeMismatch) && !errors.Is(err, storage.ErrStatusMismatch) {\n    return retryWithBackoff(op) // transient read failure\n}","preventionTips":["Use connection retry/backoff around transactions","Keep schema migrations current so assignee/status columns exist","Grant the app role SELECT on issues; monitor DB health"],"tags":["go","database","query-failure","cas"],"backgroundTag":"database-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}