{"record":{"id":"d2eae7a494b7387b","repo":"gastownhall/beads","slug":"failed-to-increment-issue-counter-after-seeding-fo","errorCode":null,"errorMessage":"failed to increment issue counter after seeding for prefix %q: %w","messagePattern":"failed to increment issue counter after seeding for prefix %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/issues.go","lineNumber":878,"sourceCode":"\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 {\n\t\t\t// Seeding found no existing numeric IDs -- insert the initial row.\n\t\t\t_, err = tx.ExecContext(ctx, \"INSERT INTO issue_counter (prefix, last_id) VALUES (?, 1)\", prefix)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to insert initial issue counter for prefix %q: %w\", prefix, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Read back the value that was atomically set by the DB engine.\n\tvar nextID int\n\terr = tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&nextID)\n\tif err != nil {","sourceCodeStart":860,"sourceCodeEnd":896,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/issues.go#L860-L896","documentation":"After seeding, the code retries the same atomic UPDATE on issue_counter. This error means the retry UPDATE statement itself failed with a SQL/driver error. Note that if seeding inserted the counter row correctly, the retry should affect a row; an error here is a statement-level failure (lock, abort, connection, missing table).","triggerScenarios":"generateIssueIDInTable in counter mode where the initial UPDATE affected 0 rows, seeding succeeded, but the retried `UPDATE issue_counter SET last_id = last_id + 1` returns an error — e.g. transaction conflict/serialization failure under concurrency, or the seed ran on a tx already marked bad.","commonSituations":"Two concurrent bd processes creating issues on the same database and racing on the counter row; Dolt MVCC conflict reported as an SQL error; long-running transaction hitting a lock timeout.","solutions":["Simply retry the issue-creation command; MVCC/lock conflicts are often transient.","Reduce concurrent writers to the same .beads database (or use bd's built-in locking) and retry.","Check the wrapped cause: if it is a lock timeout, increase the engine's lock/transaction timeout or shorten the surrounding transaction.","If persistent, verify the issue_counter row was actually written by seeding (SELECT last_id FROM issue_counter WHERE prefix=...)."],"exampleFix":"// before\nres, err = tx.ExecContext(ctx, \"UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?\", prefix)\nif err != nil {\n    return \"\", fmt.Errorf(\"failed to increment issue counter after seeding for prefix %q: %w\", prefix, err)\n}\n// after (bounded retry for transient conflicts)\nfor attempt := 0; attempt < 3; attempt++ {\n    res, err = tx.ExecContext(ctx, \"UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?\", prefix)\n    if err == nil {\n        break\n    }\n    if !isTransientSQLErr(err) {\n        return \"\", fmt.Errorf(\"failed to increment issue counter after seeding for prefix %q: %w\", prefix, err)\n    }\n    time.Sleep(50 * time.Millisecond << attempt)\n}","handlingStrategy":"retry","validationCode":"// before heavy concurrent creation, check for counter contention\nvar writers int\n_ = db.QueryRow(\"SELECT COUNT(*) FROM information_schema.processlist WHERE info LIKE '%issue_counter%'\").Scan(&writers)\nif writers > 1 { time.Sleep(time.Second) }","typeGuard":"func isTransientSQLErr(err error) bool {\n    msg := err.Error()\n    return strings.Contains(msg, \"lock\") || strings.Contains(msg, \"conflict\") || strings.Contains(msg, \"timeout\")\n}","tryCatchPattern":"var id string\nvar err error\nfor i := 0; i < 3; i++ {\n    id, err = store.CreateIssue(ctx, issue)\n    if err == nil || !strings.Contains(err.Error(), \"increment issue counter after seeding\") {\n        break\n    }\n    time.Sleep(100 * time.Millisecond << i)\n}","preventionTips":["Avoid multiple concurrent bd processes writing to the same embedded database","Batch issue creation in one process instead of parallel invocations","Keep transactions around counter updates short","Upgrade to a beads version with bounded retries on counter conflicts"],"tags":["database","concurrency","counter","mvcc"],"backgroundTag":"counter-update-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}