gastownhall/beads · error
read newest comment time from %s: %w
Error message
read newest comment time from %s: %w
What it means
NextLiveCommentTime reads MAX(created_at) for an issue's comments to stamp a new live comment monotonically after the newest one. The Scan on QueryRowContext fails for SQL reasons — missing table/column, connection failure, or cancelled context (note sql.ErrNoRows is not expected here since MAX over an aggregate always returns one row, NULL when empty). The library wraps it so comment-timestamp monotonicity cannot be silently violated.
Source
Thrown at internal/storage/issueops/derivedid.go:195
//
// This deliberately does NOT apply to the import path: an import carries the
// original timestamps and must not invent new ones. Same-second groups
// therefore still occur (imports, seeded/legacy rows, independently created
// rows on another replica), and the (created_at, id) keyset walk in
// GetIssueCommentsPageInTx remains the mechanism that keeps those groups
// consistent between paged and full reads.
//
// The cost is a bounded forward skew: a burst of N comments on one issue inside
// one second reads back spanning N seconds. That is a smaller distortion than N
// identical stamps in scrambled order, and it drains as wall-clock advances.
//
//nolint:gosec // G201: table is a hardcoded routing constant at every call site.
func NextLiveCommentTime(ctx context.Context, tx DBTX, table, issueID string, now time.Time) (time.Time, error) {
now = now.UTC().Truncate(time.Second)
var latest sql.NullTime
if err := tx.QueryRowContext(ctx, fmt.Sprintf(
`SELECT MAX(created_at) FROM %s WHERE issue_id = ?`, table), issueID).Scan(&latest); err != nil {
return time.Time{}, fmt.Errorf("read newest comment time from %s: %w", table, err)
}
if !latest.Valid {
return now, nil
}
newest := latest.Time.UTC().Truncate(time.Second)
if newest.Before(now) {
return now, nil
}
return newest.Add(time.Second), nil
}
// InsertDerivedComment inserts a comment under its content-derived id, or
// collapses onto an existing identical comment: a same-content row already in
// table (any id — it may predate the derivation) is the same logical comment,
// and the import path has always existence-checked exactly this column set
// (issue_id, author, text, created_at) rather than insert a duplicate. It
// returns the surviving row's id and whether it already existed.
//View on GitHub (pinned to 71377f2769)
Solutions
- Run migrations so the comments table has created_at and the expected index.
- Verify the table routing constant refers to an existing derived comments table.
- Check connection/transaction health; retry in a fresh transaction if aborted.
- Increase the context deadline if the query is timing out on large comment sets.
Example fix
// before: old schema lacks created_at SELECT MAX(created_at) FROM %s WHERE issue_id = ? // after: apply migration first // ALTER TABLE issues_comments ADD COLUMN created_at DATETIME; // then the query succeeds
Defensive patterns
Strategy: try-catch
Validate before calling
cols, err := tableColumns(tx, table)
if err != nil { return err }
if !slices.Contains(cols, "created_at") {
return fmt.Errorf("%s missing created_at; run migrations", table)
}
if err := ctx.Err(); err != nil { return err } Type guard
func isSchemaErr(err error) bool {
msg := err.Error()
return strings.Contains(msg, "Unknown column") || strings.Contains(msg, "doesn't exist")
} Try / catch
t, err := NextLiveCommentTime(ctx, tx, table, issueID, now)
if err != nil {
if isSchemaErr(err) {
return fmt.Errorf("comment table schema drifted; migrate: %w", err)
}
return err
} Prevention
- Run migrations whenever created_at was added in a newer version.
- Use only the documented table constants for comment routing.
- Keep per-issue comment queries indexed on issue_id.
- Preflight with `bd doctor` after restores or version upgrades.
When it happens
Trigger: Calling addIssueCommentInTx / PersistComments when the routed comments table doesn't exist or lacks created_at, or the transaction's connection is dead/cancelled.
Common situations: Schema drift on older databases (missing created_at column added by later migrations); passing a wrong table constant; context timeout during a large comment fetch.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- check comment existence in %s: %w
- scan same-content events in %s: %w
- add comment to %s: %w
- load wisp comments: %w
- read comments for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a7b0c743af32680a.
Report an issue: GitHub.