{"record":{"id":"5f4eb0d79212325f","repo":"gastownhall/beads","slug":"failed-to-check-rows-affected-for-issue-counter-pr","errorCode":null,"errorMessage":"failed to check rows affected for issue counter prefix %q: %w","messagePattern":"failed to check rows affected for issue counter prefix %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/issues.go","lineNumber":866,"sourceCode":"}\n\n// nextCounterIDTx atomically increments and returns the next sequential issue ID\n// for the given prefix within an existing transaction. Returns the full ID string\n// (e.g., \"bd-1\"). Used by both generateIssueID and generateIssueIDInTable.\nfunc nextCounterIDTx(ctx context.Context, tx *sql.Tx, prefix string) (string, error) {\n\t// Increment atomically at the DB level to avoid duplicate IDs under\n\t// concurrent transactions (GH#2002). \"last_id = last_id + 1\" is evaluated\n\t// by the DB engine atomically within Dolt's MVCC.\n\n\t// Attempt atomic increment of an existing counter row.\n\tres, err := tx.ExecContext(ctx, \"UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?\", prefix)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to increment issue counter for prefix %q: %w\", prefix, err)\n\t}\n\n\trowsAffected, err := res.RowsAffected()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to check rows affected for issue counter prefix %q: %w\", prefix, err)\n\t}\n\n\tif rowsAffected == 0 {\n\t\t// No counter row yet - seed from existing issues before proceeding to\n\t\t// avoid collisions with manually-created sequential IDs (GH#2002).\n\t\tif seedErr := seedCounterFromExistingIssuesTx(ctx, tx, prefix); seedErr != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to seed issue counter for prefix %q: %w\", prefix, seedErr)\n\t\t}\n\t\t// Retry the atomic increment after seeding.\n\t\tres, err = tx.ExecContext(ctx, \"UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?\", prefix)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to increment issue counter after seeding for prefix %q: %w\", prefix, err)\n\t\t}\n\t\trowsAffected, err = res.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to check rows affected after seeding for prefix %q: %w\", prefix, err)\n\t\t}\n\t\tif rowsAffected == 0 {","sourceCodeStart":848,"sourceCodeEnd":884,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/issues.go#L848-L884","documentation":"After the counter UPDATE succeeds, nextCounterIDTx calls res.RowsAffected() to learn whether a counter row existed. This error wraps a failure of RowsAffected() itself — the Dolt driver could not report the affected-row count for the executed statement. It is a driver-capability/protocol problem, not a data problem.","triggerScenarios":"Calling generateIssueIDInTable in counter mode immediately after a successful UPDATE when the driver's RowsAffected() returns a non-nil error (driver version lacking result-metadata support, connection in a bad state after exec, server protocol mismatch).","commonSituations":"Using an outdated or third-party SQL driver for Dolt that doesn't implement affected-rows reporting for UPDATEs; running against a proxy/middleware that strips result metadata; driver version mismatch with the Dolt server.","solutions":["Inspect the wrapped error; upgrade the Dolt SQL driver and beads to matching, current versions.","Bypass proxies/intermediaries between beads and the Dolt server to rule out metadata stripping.","Retry the operation; if transient, check server logs for connection/protocol errors at that timestamp.","As a fallback, upgrade to a beads version that treats RowsAffected errors via a read-back path instead of failing."],"exampleFix":"// before\nrowsAffected, err := res.RowsAffected()\nif err != nil {\n    return \"\", fmt.Errorf(\"failed to check rows affected for issue counter prefix %q: %w\", prefix, err)\n}\n// after (degrade gracefully by reading back the row)\nrowsAffected, err := res.RowsAffected()\nif err != nil {\n    var cur int\n    if scanErr := tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&cur); scanErr != nil {\n        return \"\", fmt.Errorf(\"failed to check rows affected for issue counter prefix %q: %w\", prefix, err)\n    }\n    rowsAffected = 1\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"func isRowsAffectedErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"rows affected\")\n}","tryCatchPattern":"id, err := store.CreateIssue(ctx, issue)\nvar wrappedErr error\nif err != nil && errors.As(err, &wrappedErr) && strings.Contains(err.Error(), \"rows affected for issue counter\") {\n    // driver capability problem: upgrade driver/beads, then retry\n    return fmt.Errorf(\"driver cannot report rows affected; upgrade driver: %w\", err)\n}","preventionTips":["Pin compatible versions of the Dolt SQL driver and beads","Do not route embedded DB traffic through proxies that strip result metadata","Test counter-mode ID generation after any driver upgrade","Report persistent RowsAffected failures to the driver maintainers"],"tags":["database","driver","rows-affected","dolt"],"backgroundTag":"rows-affected-unavailable","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}