{"record":{"id":"447d60fbabaa42bb","repo":"gastownhall/beads","slug":"failed-to-seed-issue-counter-for-prefix-q-at-d","errorCode":null,"errorMessage":"failed to seed issue_counter for prefix %q at %d: %w","messagePattern":"failed to seed issue_counter for prefix %q at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/issues.go","lineNumber":843,"sourceCode":"\t\tvar num int\n\t\tif _, parseErr := fmt.Sscanf(suffix, \"%d\", &num); parseErr == nil && fmt.Sprintf(\"%d\", num) == suffix {\n\t\t\tif num > maxNum {\n\t\t\t\tmaxNum = num\n\t\t\t}\n\t\t}\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"failed to iterate existing issues for prefix %q: %w\", prefix, err)\n\t}\n\n\t// Only insert a seed row if we found at least one numeric ID.\n\t// If no numeric IDs exist, the counter will naturally start at 1 on first use.\n\tif maxNum > 0 {\n\t\t_, err = tx.ExecContext(ctx,\n\t\t\t\"INSERT INTO issue_counter (prefix, last_id) VALUES (?, ?)\",\n\t\t\tprefix, maxNum)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to seed issue_counter for prefix %q at %d: %w\", prefix, maxNum, err)\n\t\t}\n\t}\n\n\treturn nil\n}\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)","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/issues.go#L825-L861","documentation":"Once the highest numeric suffix (maxNum) is found, the seeder inserts INSERT INTO issue_counter (prefix, last_id) to initialize the counter; failure of this insert is wrapped with the prefix and value. ID generation cannot proceed until the counter row exists.","triggerScenarios":"First create for a prefix where the counter INSERT fails — constraint violation from a concurrent seeder, schema mismatch, transaction failure, or backend unavailability.","commonSituations":"Two processes seeding the same prefix concurrently (race on the insert); databases missing the issue_counter table; read-only or full storage backend.","solutions":["Read the wrapped cause; retry the create — if another process seeded successfully, the SELECT will now find the row","Verify the issue_counter table exists with the expected (prefix, last_id) schema","Avoid concurrent initializations of a brand-new database; run one create first, then start parallel workers","Check disk space / read-only filesystem if inserts consistently fail"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Ensure counter table exists and schema matches\nconst cols = await describe(\"issue_counter\"); // expect: prefix, last_id\nif (!cols.includes(\"last_id\")) throw new Error(\"issue_counter schema mismatch; run migrations\");","typeGuard":null,"tryCatchPattern":"try {\n  await bd.create(title);\n} catch (e) {\n  if (String(e.message).includes(\"failed to seed issue_counter\")) {\n    // likely concurrent seeder or schema issue; retry create\n    await backoff();\n  } else throw e;\n}","preventionTips":["Don't run parallel creates against a brand-new database; create once, then parallelize","Run migrations so issue_counter has the expected (prefix, last_id) schema","Ensure the storage backend is writable (disk space, permissions)","Retry on insert conflicts — an existing row means another seeder succeeded"],"tags":["database","counter","insert","storage"],"backgroundTag":"counter-seed-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}