{"record":{"id":"b464edbf2ab22e78","repo":"gastownhall/beads","slug":"failed-to-insert-initial-issue-counter-for-prefix-b464ed","errorCode":null,"errorMessage":"failed to insert initial issue counter for prefix %q: %w","messagePattern":"failed to insert initial issue counter for prefix %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/helpers.go","lineNumber":256,"sourceCode":"\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\n\terr = tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&nextID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read issue counter after increment for prefix %q: %w\", prefix, err)\n\t}\n\treturn fmt.Sprintf(\"%s-%d\", prefix, nextID), nil\n}\n\n// SeedCounterFromExistingIssuesTx scans existing issues to find the highest numeric suffix\n// for the given prefix, then seeds the issue_counter table if no row exists yet.\nfunc SeedCounterFromExistingIssuesTx(ctx context.Context, tx DBTX, prefix string) error {\n\tvar existing int\n\terr := tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&existing)\n\tif err == nil {","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/helpers.go#L238-L274","documentation":"NextCounterIDTx failed to insert the first row (last_id=1) into the issue_counter table for a prefix. This fallback runs only after the UPDATE increment matched 0 rows and seeding from existing issues also left no row (e.g. no existing issues with that prefix). The wrapped SQL error is the real cause (table missing, constraint violation, permissions, DB connection).","triggerScenarios":"Calling GenerateIssueIDInTable (counter mode) with a prefix that has no issue_counter row and no existing issues: the INSERT INTO issue_counter (prefix, last_id) VALUES (?, 1) fails, typically because the issue_counter table does not exist (old schema, not migrated) or the tx/DB is broken.","commonSituations":"Dolt database created before the counter-mode schema migration; counter mode enabled via issue_id_mode=counter but migrations not applied; a custom prefix used for the first issue ever; read-only or crashed connection mid-transaction.","solutions":["Run schema migrations so the issue_counter table exists (bd should apply migrations on open; verify with a SELECT 1 FROM issue_counter query).","Pre-seed the counter manually: INSERT INTO issue_counter (prefix, last_id) VALUES ('<PREFIX>', 0), then retry ID generation.","Read the wrapped %w error: if it is 'no such table', apply migrations; if it is a connection/lock error, retry the operation.","Check DB connectivity and that the transaction is still valid (not rolled back/timed out) before calling again."],"exampleFix":"// before: counter mode on an unmigrated DB fails with 'no such table: issue_counter'\n// after\n// apply migrations at startup before generating IDs\ndb.SetDefaultMigrationTimeout(30 * time.Second)\nif err := storage.Migrate(ctx, db); err != nil { log.Fatal(err) }\nid, err := storage.GenerateIssueIDInTable(ctx, tx, \"issues\", \"PROJ\", issue)","handlingStrategy":"retry","validationCode":"var exists int\nif err := tx.QueryRowContext(ctx, \"SELECT COUNT(*) FROM issue_counter\").Scan(&exists); err != nil {\n    return fmt.Errorf(\"issue_counter table missing; run migrations first: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"id, err := storage.GenerateIssueIDInTable(ctx, tx, \"issues\", prefix, issue)\nif err != nil {\n    var inner error\n    if errors.As(err, &inner) && strings.Contains(inner.Error(), \"no such table\") {\n        if mErr := storage.Migrate(ctx, db); mErr != nil { log.Fatal(mErr) }\n        id, err = storage.GenerateIssueIDInTable(ctx, tx, \"issues\", prefix, issue)\n    }\n}","preventionTips":["Always run schema migrations at startup before any bd storage operation.","After enabling issue_id_mode=counter, smoke-test ID generation in CI.","Pre-seed counters for custom prefixes when provisioning a new project."],"tags":["database","counter","schema-migration","go"],"backgroundTag":"missing-table-issue-counter","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}