gastownhall/beads · error

db: CommentSQLRepository.Insert: %w

Error message

db: CommentSQLRepository.Insert: %w

What it means

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.

Source

Thrown at internal/storage/domain/db/comment.go:123

	return result, nil
}

func (r *commentSQLRepositoryImpl) IterByIssueID(ctx context.Context, issueID string, opts domain.CommentOpts) (storage.Iter[types.Comment], error) {
	bulk, err := r.ListByIssueIDs(ctx, []string{issueID}, opts)
	if err != nil {
		return nil, err
	}
	return storage.NewSliceIter(bulk[issueID]), nil
}

func (r *commentSQLRepositoryImpl) Insert(ctx context.Context, issueID, author, text string, opts domain.CommentOpts) (*types.Comment, error) {
	// Live add: advance past the issue's newest comment so a burst inside one
	// second still reads back in write order (issueops.NextLiveCommentTime).
	// InsertRecord honors a supplied CreatedAt verbatim, which is what keeps
	// imported comments on their original timestamps.
	stamp, err := issueops.NextLiveCommentTime(ctx, r.runner, pickCommentTable(opts.UseWispsTable), issueID, time.Now())
	if err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.Insert: %w", err)
	}
	return r.InsertRecord(ctx, &types.Comment{IssueID: issueID, Author: author, Text: text, CreatedAt: stamp}, opts)
}

func (r *commentSQLRepositoryImpl) InsertRecord(ctx context.Context, comment *types.Comment, opts domain.CommentOpts) (*types.Comment, error) {
	if comment == nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: comment must not be nil")
	}
	copy := *comment
	if copy.IssueID == "" {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: issueID must not be empty")
	}

	issueTable := pickIssueTable(opts.UseWispsTable)
	var exists bool
	//nolint:gosec // G201: issueTable is one of two hardcoded constants
	if err := r.runner.QueryRowContext(ctx,
		fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM %s WHERE id = ?)", issueTable), copy.IssueID).Scan(&exists); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run migrations so the selected comment table exists; check dberrors.IsTableNotExist on the wrapped cause.
  2. Verify UseWispsTable matches the table your issue lives in.
  3. Inspect the wrapped error for cancellation/timeouts and raise the context deadline.
  4. If using InsertRecord with an explicit CreatedAt, this path is bypassed — precompute a stamp yourself when appropriate.

Example fix

// before: inserting into wisp_comments on a DB without wisp schema
repo.Insert(ctx, issueID, "alice", "hello", domain.CommentOpts{UseWispsTable: true})

// after: ensure wisp schema exists (or flip UseWispsTable) before inserting
if err := ensureWispSchema(ctx, conn); err != nil { return err }
c, err := repo.Insert(ctx, issueID, "alice", "hello", opts)
Defensive patterns

Strategy: validation

Validate before calling

table := "comments"
if opts.UseWispsTable { table = "wisp_comments" }
var exists int
err := conn.QueryRowContext(ctx,
    "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?",
    table).Scan(&exists)
if err != nil || exists == 0 { return fmt.Errorf("cannot insert comment: %s missing", table) }

Type guard

func isInsertStampError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "CommentSQLRepository.Insert:")
}

Try / catch

c, err := repo.Insert(ctx, issueID, author, text, opts)
if err != nil && isInsertStampError(err) {
    if dberrors.IsTableNotExist(err) { migrate(); c, err = repo.Insert(ctx, issueID, author, text, opts) }
    if err != nil { return nil, err }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/da90f583f899a76c. Report an issue: GitHub.