{"record":{"id":"e6aa4502ae3c8cd6","repo":"gastownhall/beads","slug":"failed-to-seed-issue-counter-for-prefix-q-w-e6aa45","errorCode":null,"errorMessage":"failed to seed issue counter for prefix %q: %w","messagePattern":"failed to seed issue counter for prefix %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/helpers.go","lineNumber":243,"sourceCode":"\t}\n\treturn idMode == \"counter\", nil\n}\n\n// NextCounterIDTx atomically increments and returns the next sequential issue ID.\nfunc NextCounterIDTx(ctx context.Context, tx DBTX, prefix string) (string, error) {\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\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\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_, 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\tvar nextID int","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/helpers.go#L225-L261","documentation":"When no issue_counter row exists for the prefix (rowsAffected == 0), NextCounterIDTx calls SeedCounterFromExistingIssuesTx to derive last_id from existing issues. This error wraps any failure from that seeding step — e.g. the SELECT over existing issues failed, or the seed INSERT hit a constraint. The counter cannot be advanced until seeding succeeds.","triggerScenarios":"First issue ever created with a new prefix while the issues or issue_counter tables are unreadable or unwritable; the seed INSERT conflicts with a concurrently inserted counter row (duplicate key); connection loss during the seed queries.","commonSituations":"Fresh database or freshly added prefix with concurrent writers racing to seed the counter; corrupted issues table making the max-suffix scan fail; schema migration that dropped issue_counter but left issues behind; read-only database refusing the seed INSERT.","solutions":["Inspect the wrapped seed error: if it is a duplicate-key on issue_counter, another writer seeded concurrently — simply retry the operation.","Run the schema/migration check to ensure both issues and issue_counter tables exist and are writable.","Retry issue creation with backoff — the seed race is benign and resolves after the winner commits.","Check DB write permissions and read-only mode if the seed INSERT is being refused.","Avoid multiple concurrent first-writes per prefix in tests and tools by serializing initial issue creation."],"exampleFix":"// before: for _, p := range prefixes { go createIssue(p) } // races seeding issue_counter. // after: single-flight per prefix — g.Go(func() error { return createIssueSerialized(p) }); g.Wait()","handlingStrategy":"retry","validationCode":"if _, err := tx.ExecContext(ctx, \"INSERT IGNORE INTO issue_counter (prefix, last_id) VALUES (?, 0)\", prefix); err != nil { return err } // ensure counter row exists before concurrent creation","typeGuard":"func isSeedErr(err error) bool { return err != nil && strings.Contains(err.Error(), \"failed to seed issue counter for prefix\") }","tryCatchPattern":"id, err := GenerateIssueIDInTable(ctx, tx, prefix, issue); if isSeedErr(err) { return retryWithBackoff(ctx, 3, 100*time.Millisecond, func() error { id, err = GenerateIssueIDInTable(ctx, tx, prefix, issue); return err }) }","preventionTips":["Pre-seed the issue_counter row for each prefix at bootstrap or migration time.","Serialize the first issue creation per prefix (single-flight) to avoid seed races.","Verify the issues table is readable before bulk inserts so the max-suffix scan works.","Check the DB is not in read-only mode before initial writes."],"tags":["database","sql","counter","seed","race"],"backgroundTag":"counter-seed-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}