{"record":{"id":"d547c380a9320eef","repo":"gastownhall/beads","slug":"check-comment-existence-in-s-w","errorCode":null,"errorMessage":"check comment existence in %s: %w","messagePattern":"check comment existence in (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/derivedid.go","lineNumber":225,"sourceCode":"// InsertDerivedComment inserts a comment under its content-derived id, or\n// collapses onto an existing identical comment: a same-content row already in\n// table (any id — it may predate the derivation) is the same logical comment,\n// and the import path has always existence-checked exactly this column set\n// (issue_id, author, text, created_at) rather than insert a duplicate. It\n// returns the surviving row's id and whether it already existed.\n//\n//nolint:gosec // G201: table is a hardcoded routing constant at every call site.\nfunc InsertDerivedComment(ctx context.Context, tx DBTX, table, issueID, author, text, createdAt string) (id string, existed bool, err error) {\n\terr = tx.QueryRowContext(ctx, fmt.Sprintf(`\n\t\tSELECT id FROM %s\n\t\tWHERE issue_id = ? AND author = ? AND text = ? AND created_at = ?\n\t\tORDER BY id LIMIT 1`, table),\n\t\tissueID, author, text, createdAt).Scan(&id)\n\tif err == nil {\n\t\treturn id, true, nil\n\t}\n\tif err != sql.ErrNoRows {\n\t\treturn \"\", false, fmt.Errorf(\"check comment existence in %s: %w\", table, err)\n\t}\n\tdigest := rowid.Digest([]sql.NullString{str(issueID), str(author), str(text), str(createdAt)})\n\tid = rowid.New(table, 0, digest)\n\tif _, err := tx.ExecContext(ctx, fmt.Sprintf(`\n\t\tINSERT INTO %s (id, issue_id, author, text, created_at)\n\t\tVALUES (?, ?, ?, ?, ?)`, table),\n\t\tid, issueID, author, text, createdAt); err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"add comment to %s: %w\", table, err)\n\t}\n\treturn id, false, nil\n}\n\n// InsertDerivedCompactionSnapshot inserts a compaction_snapshots row under\n// its content-derived id, with the same ordinal discipline as events. Two\n// clones compacting the same issue at the same tier in the same second\n// produce byte-identical snapshots and therefore the same id.\nfunc InsertDerivedCompactionSnapshot(ctx context.Context, tx DBTX, issueID string, level int, snapshotJSON []byte, createdAt string) error {\n\tif createdAt == \"\" {","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/derivedid.go#L207-L243","documentation":"InsertDerivedComment first probes for an existing identical comment row (content-derived dedup); this wraps any probe error other than sql.ErrNoRows. It means the existence-check SELECT (issue_id, author, text, created_at with NULL-safe comparison, ORDER BY id LIMIT 1) failed at the SQL level — schema mismatch, connection failure, or cancelled context.","triggerScenarios":"addIssueCommentInTx / PersistComments run against a comments table missing expected columns, a wrong table constant, or with a broken connection inside the transaction.","commonSituations":"Databases created before comment-table migrations; hand-edited schemas; Dolt connection dropped mid-transaction during import (PersistComments).","solutions":["Apply schema migrations so the comments table matches the expected shape.","Confirm the table constant routes to an existing derived comments table.","Retry in a fresh transaction if the connection was aborted.","Read the wrapped driver error (`errors.Unwrap` chain) for the precise SQL message."],"exampleFix":"// before\nif err != sql.ErrNoRows {\n\treturn \"\", false, fmt.Errorf(\"check comment existence in %s: %w\", table, err)\n}\n// after: also handle context cancellation distinctly\nif err != sql.ErrNoRows {\n\tif ctx.Err() != nil { return \"\", false, ctx.Err() }\n\treturn \"\", false, fmt.Errorf(\"check comment existence in %s: %w\", table, err)\n}","handlingStrategy":"try-catch","validationCode":"if err := ctx.Err(); err != nil { return err }\ncols, err := tableColumns(tx, table)\nif err != nil { return err }\nfor _, c := range []string{\"id\", \"issue_id\", \"author\", \"text\", \"created_at\"} {\n\tif !slices.Contains(cols, c) {\n\t\treturn fmt.Errorf(\"%s missing %s; migrate first\", table, c)\n\t}\n}","typeGuard":"func isNonNoRowsQueryErr(err error) bool {\n\treturn err != nil && !errors.Is(err, sql.ErrNoRows)\n}","tryCatchPattern":"id, created, err := InsertDerivedComment(ctx, tx, table, issueID, author, text, createdAt)\nif err != nil {\n\tif errors.Is(err, context.Canceled) { return err }\n\tif strings.Contains(err.Error(), \"Unknown column\") {\n\t\treturn fmt.Errorf(\"migrate comment schema: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Ensure comment-table migrations ran before PersistComments imports.","Don't reuse transactions after a failed statement; start fresh.","Check ctx cancellation separately from SQL errors for clean diagnostics.","Import comments sequentially per issue to avoid probe/insert races."],"tags":["sql","query-error","storage","comments"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}