{"record":{"id":"85cd0a41abb81990","repo":"gastownhall/beads","slug":"get-issue-comments-from-s-w","errorCode":null,"errorMessage":"get issue comments from %s: %w","messagePattern":"get issue comments from (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/comments.go","lineNumber":31,"sourceCode":"\n// GetIssueCommentsInTx retrieves comments for an issue within an existing\n// transaction. Automatically routes to wisp_comments if the ID is an active wisp.\n//\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc GetIssueCommentsInTx(ctx context.Context, tx DBTX, issueID string) ([]*types.Comment, error) {\n\ttable := \"comments\"\n\tif IsActiveWispInTx(ctx, tx, issueID) {\n\t\ttable = \"wisp_comments\"\n\t}\n\n\trows, err := tx.QueryContext(ctx, fmt.Sprintf(`\n\t\tSELECT id, issue_id, author, text, created_at\n\t\tFROM %s\n\t\tWHERE issue_id = ?\n\t\tORDER BY created_at ASC, id ASC\n\t`, table), issueID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get issue comments from %s: %w\", table, err)\n\t}\n\tdefer rows.Close()\n\n\tvar comments []*types.Comment\n\tfor rows.Next() {\n\t\tvar c types.Comment\n\t\tif err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"get issue comments: scan: %w\", err)\n\t\t}\n\t\tcomments = append(comments, &c)\n\t}\n\treturn comments, rows.Err()\n}\n\n// Comment page-read tuning. Mirrors the EventsSince keyset clamp: an unbounded\n// page defeats the purpose of paging a long thread, so a non-positive limit\n// falls back to the default and any larger request is capped.\nconst (","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/comments.go#L13-L49","documentation":"GetIssueCommentsInTx fails when the SELECT of comments for an issue (from comments or wisp_comments, chosen by wisp routing) returns a driver error. The failure is wrapped as \"get issue comments from %s: %w\" naming which table was queried. A missing issue is not this error — routing falls back to comments and returns an empty list; this is a query/infrastructure failure only.","triggerScenarios":"Calling GetIssueComments (or HydrateIssueOperationResult) when the connection drops, the context is cancelled mid-query, or the comments/wisp_comments table is missing/locked; schema drift after a partial migration.","commonSituations":"Reading comments on a remote Dolt server over a flaky link; running an older bd binary against a newer schema (or vice versa) so wisp_comments does not exist; concurrent DOLT schema changes during the read.","solutions":["Unwrap the error to see the driver cause; retry the read (reads are idempotent) on transient errors.","Run schema migrations / bd doctor to confirm comments and wisp_comments tables exist.","Increase the context timeout for large threads on slow links."],"exampleFix":"// before\ncomments, err := store.GetIssueComments(ctx, id)\n\n// after\ncomments, err := store.GetIssueComments(ctx, id)\nif err != nil && isTransient(err) { // driver.ErrBadConn etc.\n\tcomments, err = store.GetIssueComments(ctx, id) // idempotent retry\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"comments, err := store.GetIssueComments(ctx, id)\nif err != nil {\n\tif isTransientDB(err) { // ErrBadConn, deadline, lock timeout\n\t\tcomments, err = store.GetIssueComments(ctx, id) // reads are idempotent\n\t}\n\tif err != nil { return fmt.Errorf(\"read comments: %w\", err) }\n}","preventionTips":["Retry idempotent reads once on transient driver errors before failing.","Keep bd binary and database schema versions aligned to avoid missing wisp_comments.","Use adequate context timeouts for large threads; avoid cancelling mid-scan."],"tags":["database","sql","comments"],"backgroundTag":"database-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}