gastownhall/beads · error

scan event: %w

Error message

scan event: %w

What it means

scanEvents iterates result rows from an events query and scans each row into a types.Event. This error wraps rows.Scan failing for a particular row — usually a column count or type mismatch between the SELECT and the scan targets (e.g. NULL handling or changed column order/type). The whole event listing fails rather than returning partial data.

Source

Thrown at internal/storage/issueops/events.go:142

	query := `
		SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at
		FROM events
		WHERE created_at >= ? AND ((created_at > ?) OR (id > ?))`
	if issueID != "" {
		query += " AND issue_id = ?"
	}
	query += fmt.Sprintf(" ORDER BY created_at ASC, id ASC LIMIT %d", limit)
	return query
}

func scanEvents(rows *sql.Rows) ([]*types.Event, error) {
	var events []*types.Event
	for rows.Next() {
		var event types.Event
		var oldValue, newValue, comment sql.NullString
		if err := rows.Scan(&event.ID, &event.IssueID, &event.EventType, &event.Actor,
			&oldValue, &newValue, &comment, &event.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan event: %w", err)
		}
		if oldValue.Valid {
			event.OldValue = &oldValue.String
		}
		if newValue.Valid {
			event.NewValue = &newValue.String
		}
		if comment.Valid {
			event.Comment = &comment.String
		}
		events = append(events, &event)
	}
	return events, rows.Err()
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Compare the events table schema against the SELECT column list in the issueops events query and align them.
  2. Run the library's schema migration to restore the expected events table shape.
  3. Check for NULL/empty strings in old_value/new_value/comment columns; they are scanned via sql.NullString so true type mismatches are the culprit.
  4. Inspect the wrapped error for the exact column index that failed.

Example fix

// before
var oldValue string
rows.Scan(&event.ID, &oldValue) // fails on NULL
// after
var oldValue sql.NullString
rows.Scan(&event.ID, &oldValue)
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify table shape before scanning:
rows, err := tx.QueryContext(ctx, "SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at FROM events LIMIT 1")
if err != nil {
	return fmt.Errorf("events table schema mismatch: %w", err)
}
rows.Close()

Try / catch

events, err := scanEvents(rows)
if err != nil {
	return fmt.Errorf("reading event rows failed (schema drift?): %w", err)
}

Prevention

When it happens

Trigger: Any events query returning rows whose shape does not match Scan(&ID, &IssueID, &EventType, &Actor, oldValue, newValue, comment, &CreatedAt): schema drift, a driver that returns unexpected types for nullable columns, or corrupted rows.

Common situations: Database migrated between versions so the events table gained/dropped columns; custom Dolt schema edits; driver type mapping changes after a driver upgrade.

Related errors


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