{"record":{"id":"faf0d12ade4d93dc","repo":"gastownhall/beads","slug":"failed-to-read-issue-counter-after-increment-for-p","errorCode":null,"errorMessage":"failed to read issue counter after increment for prefix %q: %w","messagePattern":"failed to read issue counter after increment for prefix %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/issues.go","lineNumber":897,"sourceCode":"\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 {\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// isCounterModeTx checks whether issue_id_mode=counter is configured.\nfunc isCounterModeTx(ctx context.Context, tx *sql.Tx) (bool, error) {\n\tvar idMode string\n\terr := tx.QueryRowContext(ctx, \"SELECT value FROM config WHERE `key` = ?\", \"issue_id_mode\").Scan(&idMode)\n\tif err != nil && err != sql.ErrNoRows {\n\t\treturn false, fmt.Errorf(\"failed to read issue_id_mode config: %w\", err)\n\t}\n\treturn idMode == \"counter\", nil\n}\n\n// generateHashID creates a hash-based ID for a top-level issue.\n// Uses base36 encoding (0-9, a-z) for better information density than hex.\nfunc generateHashID(prefix, title, description, creator string, timestamp time.Time, length, nonce int) string {\n\treturn idgen.GenerateHashID(prefix, title, description, creator, timestamp, length, nonce)","sourceCodeStart":879,"sourceCodeEnd":915,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/issues.go#L879-L915","documentation":"After incrementing the counter, the code reads back `SELECT last_id FROM issue_counter WHERE prefix = ?` to form the new issue ID. This error wraps failure of that read: most notably sql.ErrNoRows when the counter row unexpectedly vanished (missing INSERT path, concurrent delete), or a scan/connection error. Without this value the new ID cannot be produced, so ID generation aborts.","triggerScenarios":"Counter-mode ID generation where the increment UPDATE ran (or was skipped via a broken rowsAffected path) but the SELECT returns no row or errors — concurrent transaction deleted/never created the row, wrong database/branch context, or connection failure during query.","commonSituations":"Concurrent writers where one process rolls back the counter row; operating against a different Dolt branch/database than the one the counter was written to; a connection dropped between UPDATE and SELECT.","solutions":["Check the wrapped cause: if ErrNoRows, verify another process isn't deleting/resetting issue_counter and that you're on the expected database/branch.","Retry the ID generation; a transient connection drop will usually succeed on retry.","Confirm the UPDATE path actually located the row (re-run with rowsAffected handling fixed) so the SELECT finds it.","Inspect issue_counter contents directly (SELECT * FROM issue_counter WHERE prefix='<p>') to see if the row exists."],"exampleFix":"// before\nerr = tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&nextID)\nif err != nil {\n    return \"\", fmt.Errorf(\"failed to read issue counter after increment for prefix %q: %w\", prefix, err)\n}\n// after\nerr = tx.QueryRowContext(ctx, \"SELECT last_id FROM issue_counter WHERE prefix = ?\", prefix).Scan(&nextID)\nif err == sql.ErrNoRows {\n    return \"\", fmt.Errorf(\"issue counter row missing for prefix %q after increment; check concurrent writers/branch\", prefix)\n} else if err != nil {\n    return \"\", fmt.Errorf(\"failed to read issue counter after increment for prefix %q: %w\", prefix, err)\n}","handlingStrategy":"retry","validationCode":"var n int\nif err := db.QueryRow(\"SELECT COUNT(*) FROM issue_counter WHERE prefix = ?\", prefix).Scan(&n); err != nil || n == 0 {\n    return fmt.Errorf(\"counter row for %q missing; concurrent writers or wrong branch\", prefix)\n}","typeGuard":"func isCounterRowMissing(err error) bool {\n    return errors.Is(err, sql.ErrNoRows) || strings.Contains(err.Error(), \"read issue counter after increment\")\n}","tryCatchPattern":"id, err := store.CreateIssue(ctx, issue)\nif err != nil && strings.Contains(err.Error(), \"read issue counter after increment\") {\n    if isCounterRowMissing(err) {\n        return fmt.Errorf(\"counter row vanished; check concurrent writers and current branch: %w\", err)\n    }\n    // transient read failure: retry once\n    id, err = store.CreateIssue(ctx, issue)\n}","preventionTips":["Never run maintenance (branch reset, counter deletes) while issues are being created","Confirm all writers target the same Dolt branch/database","Use one writer process for embedded databases","Retry transient failures; investigate repeats via SELECT on issue_counter"],"tags":["database","counter","select","concurrency"],"backgroundTag":"counter-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}