gastownhall/beads · error

db: CommentSQLRepository.ListByIssueIDs: %w

Error message

db: CommentSQLRepository.ListByIssueIDs: %w

What it means

ListByIssueIDs issues a SELECT of id, issue_id, author, text, created_at from `comments`/`wisp_comments` for the given issue IDs, ordered for deterministic grouping. This error wraps the QueryContext call failing before any rows are produced — the query itself was rejected (missing table, bad connection, canceled context). It is also propagated to callers of IterByIssueID.

Source

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

		return result, nil
	}
	placeholders := make([]string, len(issueIDs))
	args := make([]any, len(issueIDs))
	for i, id := range issueIDs {
		placeholders[i] = "?"
		args[i] = id
	}
	table := pickCommentTable(opts.UseWispsTable)
	//nolint:gosec // G201: table is one of two hardcoded constants
	q := fmt.Sprintf(`
		SELECT id, issue_id, author, text, created_at
		FROM %s
		WHERE issue_id IN (%s)
		ORDER BY issue_id, created_at ASC, id ASC
	`, table, strings.Join(placeholders, ","))
	rows, err := r.runner.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var c types.Comment
		if err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {
			return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: scan: %w", err)
		}
		cc := c
		result[c.IssueID] = append(result[c.IssueID], &cc)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: rows: %w", err)
	}
	return result, nil
}

func (r *commentSQLRepositoryImpl) IterByIssueID(ctx context.Context, issueID string, opts domain.CommentOpts) (storage.Iter[types.Comment], error) {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Chunk large issueIDs slices (e.g. 500–1000 per query) to avoid IN-clause/placeholder limits.
  2. Ensure migrations created the selected table; match UseWispsTable to the schema you actually have.
  3. Inspect the wrapped driver error for connectivity vs schema causes (dberrors.IsTableNotExist).
  4. Retry on transient connection errors.

Example fix

// before: one query with unbounded ID list
comments, err := repo.ListByIssueIDs(ctx, allThousandIDs, opts)

// after: chunk the ID list
for chunk := range slices.Chunk(allThousandIDs, 500) {
    batch, err := repo.ListByIssueIDs(ctx, chunk, opts)
    if err != nil { return err }
    // merge batch
}
Defensive patterns

Strategy: validation

Validate before calling

const maxInClause = 500
if len(issueIDs) > maxInClause {
    return fmt.Errorf("split %d issue IDs into chunks of %d", len(issueIDs), maxInClause)
}

Type guard

func isListQueryError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "ListByIssueIDs:") &&
        !strings.Contains(err.Error(), "scan:") && !strings.Contains(err.Error(), "rows:")
}

Try / catch

comments, err := repo.ListByIssueIDs(ctx, ids, opts)
if err != nil && isListQueryError(err) {
    if dberrors.IsTableNotExist(err) { migrate(); comments, err = repo.ListByIssueIDs(ctx, ids, opts) }
    if err != nil { return nil, err }
}

Prevention

When it happens

Trigger: Calling ListByIssueIDs (or IterByIssueID) with a huge issueIDs list overflowing placeholders; querying `wisp_comments` when the wisp schema is absent; canceled context; broken connection.

Common situations: Bulk dashboards passing thousands of issue IDs at once; databases created before wisp tables were introduced; connection pool exhaustion under load.

Related errors


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