{"record":{"id":"18d9cdf59375b03b","repo":"gastownhall/beads","slug":"failed-to-insert-comment-for-s-w","errorCode":null,"errorMessage":"failed to insert comment for %s: %w","messagePattern":"failed to insert comment for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/create.go","lineNumber":800,"sourceCode":"\tvar result CreateIssueResult\n\tif len(issue.Comments) == 0 {\n\t\treturn result, nil\n\t}\n\tcommentTable := \"comments\"\n\tif IsWisp(issue) {\n\t\tcommentTable = \"wisp_comments\"\n\t}\n\tfor _, comment := range issue.Comments {\n\t\tcreatedAt := comment.CreatedAt\n\t\tif createdAt.IsZero() {\n\t\t\t// No supplied timestamp: this is a live comment, so stamp it the\n\t\t\t// same way AddIssueComment does — one second past the issue's\n\t\t\t// newest comment when the clock second would collide. Otherwise\n\t\t\t// several such comments in one create share a second and read back\n\t\t\t// in content-digest order rather than the order they were listed.\n\t\t\tstamped, err := NextLiveCommentTime(ctx, tx, commentTable, issue.ID, time.Now())\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"failed to insert comment for %s: %w\", issue.ID, err)\n\t\t\t}\n\t\t\tcreatedAt = stamped\n\t\t}\n\t\tcreatedAtText := FormatAuxTime(createdAt)\n\t\tif comment.ID == \"\" {\n\t\t\t// No incoming id (fresh comment): content-derived id, collapsing\n\t\t\t// onto an identical existing row exactly like the import dedup.\n\t\t\tid, existed, err := InsertDerivedComment(ctx, tx, commentTable, issue.ID, comment.Author, comment.Text, createdAtText)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"failed to insert comment for %s: %w\", issue.ID, err)\n\t\t\t}\n\t\t\tcomment.ID = id\n\t\t\tif !existed {\n\t\t\t\tresult.markChanged(commentTable)\n\t\t\t\tresult.persistedComments = append(result.persistedComments, EventComment{\n\t\t\t\t\tID: id, Author: comment.Author, Text: comment.Text, CreatedAt: createdAt, Source: CommentSourceStructured,\n\t\t\t\t})\n\t\t\t}","sourceCodeStart":782,"sourceCodeEnd":818,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/create.go#L782-L818","documentation":"PersistComments stamps each structured comment with a timestamp via NextLiveCommentTime so comments created in the same second as the issue's newest comment still order deterministically. If querying/advancing that timestamp fails (a database error from the underlying time query), the comment insert is aborted and the error is wrapped with the issue ID. The whole create transaction's comment persistence stops at that point.","triggerScenarios":"Creating an issue with structured comments via CreateIssueInTxWithResult when NextLiveCommentTime's query against the comment table fails — e.g. the comment table is missing/corrupt, the connection dropped mid-transaction, or the context was cancelled.","commonSituations":"Database locked or unavailable during bulk import; schema migration mismatch leaving an old comment table; ctx timeout expiring during a large multi-comment create; disk-full or I/O errors on the embedded Dolt database.","solutions":["Inspect the wrapped error (%w) for the underlying DB failure (locked DB, no such table, ctx deadline) and fix that root cause.","Retry the create once the database is reachable/locks are released.","Verify the comment table schema matches the expected migration version (run the repo's schema migration/doctor).","Increase the context timeout when creating issues with many comments in one transaction."],"exampleFix":"// before\nctx := context.Background() // no deadline control; large create times out\nresult, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts)\n// after\nctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\ndefer cancel()\nresult, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts)","handlingStrategy":"retry","validationCode":"// verify DB reachable and comment table present before create\nif err := tx.PingContext(ctx); err != nil { return err }\nvar n int\nif err := tx.QueryRowContext(ctx,\n    \"SELECT COUNT(*) FROM sqlite_master WHERE name = 'issue_comments'\").Scan(&n); err != nil || n == 0 {\n    return fmt.Errorf(\"comment table missing; run migrations\")\n}","typeGuard":null,"tryCatchPattern":"result, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts)\nif err != nil && strings.Contains(err.Error(), \"failed to insert comment\") {\n    if isTransient(err) { // locked / connection reset / deadline\n        return retryWithBackoff(ctx, func() error {\n            _, e := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts)\n            return e\n        })\n    }\n    return err\n}","preventionTips":["Set generous context timeouts for creates with many comments.","Keep the schema migrated to the current version.","Retry transient DB errors with backoff.","Monitor DB health (locks, disk space) before bulk operations."],"tags":["storage","comments","database"],"backgroundTag":"comment-persistence-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}