{"record":{"id":"da90f583f899a76c","repo":"gastownhall/beads","slug":"db-commentsqlrepository-insert-w","errorCode":null,"errorMessage":"db: CommentSQLRepository.Insert: %w","messagePattern":"db: CommentSQLRepository\\.Insert: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/comment.go","lineNumber":123,"sourceCode":"\treturn result, nil\n}\n\nfunc (r *commentSQLRepositoryImpl) IterByIssueID(ctx context.Context, issueID string, opts domain.CommentOpts) (storage.Iter[types.Comment], error) {\n\tbulk, err := r.ListByIssueIDs(ctx, []string{issueID}, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn storage.NewSliceIter(bulk[issueID]), nil\n}\n\nfunc (r *commentSQLRepositoryImpl) Insert(ctx context.Context, issueID, author, text string, opts domain.CommentOpts) (*types.Comment, error) {\n\t// Live add: advance past the issue's newest comment so a burst inside one\n\t// second still reads back in write order (issueops.NextLiveCommentTime).\n\t// InsertRecord honors a supplied CreatedAt verbatim, which is what keeps\n\t// imported comments on their original timestamps.\n\tstamp, err := issueops.NextLiveCommentTime(ctx, r.runner, pickCommentTable(opts.UseWispsTable), issueID, time.Now())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.Insert: %w\", err)\n\t}\n\treturn r.InsertRecord(ctx, &types.Comment{IssueID: issueID, Author: author, Text: text, CreatedAt: stamp}, opts)\n}\n\nfunc (r *commentSQLRepositoryImpl) InsertRecord(ctx context.Context, comment *types.Comment, opts domain.CommentOpts) (*types.Comment, error) {\n\tif comment == nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.InsertRecord: comment must not be nil\")\n\t}\n\tcopy := *comment\n\tif copy.IssueID == \"\" {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.InsertRecord: issueID must not be empty\")\n\t}\n\n\tissueTable := pickIssueTable(opts.UseWispsTable)\n\tvar exists bool\n\t//nolint:gosec // G201: issueTable is one of two hardcoded constants\n\tif err := r.runner.QueryRowContext(ctx,\n\t\tfmt.Sprintf(\"SELECT EXISTS(SELECT 1 FROM %s WHERE id = ?)\", issueTable), copy.IssueID).Scan(&exists); err != nil {","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/comment.go#L105-L141","documentation":"Insert first calls issueops.NextLiveCommentTime to compute a created_at stamp that is strictly newer than the issue's newest comment, guaranteeing in-order reads within the same second. This error wraps any failure from that timestamp computation (which itself queries the comment table). The comment is not inserted when this fires.","triggerScenarios":"Calling CommentSQLRepository.Insert(ctx, issueID, author, text, opts) when NextLiveCommentTime cannot read the latest comment time — e.g. the comment table (`comments`/`wisp_comments`) is missing, the query errors, or the context is canceled.","commonSituations":"Inserting comments into a database whose comment table wasn't migrated; UseWispsTable set incorrectly so the wrong table is probed; concurrent insert storms making the timestamp query contend.","solutions":["Run migrations so the selected comment table exists; check dberrors.IsTableNotExist on the wrapped cause.","Verify UseWispsTable matches the table your issue lives in.","Inspect the wrapped error for cancellation/timeouts and raise the context deadline.","If using InsertRecord with an explicit CreatedAt, this path is bypassed — precompute a stamp yourself when appropriate."],"exampleFix":"// before: inserting into wisp_comments on a DB without wisp schema\nrepo.Insert(ctx, issueID, \"alice\", \"hello\", domain.CommentOpts{UseWispsTable: true})\n\n// after: ensure wisp schema exists (or flip UseWispsTable) before inserting\nif err := ensureWispSchema(ctx, conn); err != nil { return err }\nc, err := repo.Insert(ctx, issueID, \"alice\", \"hello\", opts)","handlingStrategy":"validation","validationCode":"table := \"comments\"\nif opts.UseWispsTable { table = \"wisp_comments\" }\nvar exists int\nerr := conn.QueryRowContext(ctx,\n    \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?\",\n    table).Scan(&exists)\nif err != nil || exists == 0 { return fmt.Errorf(\"cannot insert comment: %s missing\", table) }","typeGuard":"func isInsertStampError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"CommentSQLRepository.Insert:\")\n}","tryCatchPattern":"c, err := repo.Insert(ctx, issueID, author, text, opts)\nif err != nil && isInsertStampError(err) {\n    if dberrors.IsTableNotExist(err) { migrate(); c, err = repo.Insert(ctx, issueID, author, text, opts) }\n    if err != nil { return nil, err }\n}","preventionTips":["Migrate comment tables before writes","Keep UseWispsTable consistent with issue storage","Avoid canceling contexts mid-insert; use InsertRecord when you control timestamps"],"tags":["database","sql","insert","go"],"backgroundTag":"table-not-exist","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}