gastownhall/beads · error

scan issue counts: %w

Error message

scan issue counts: %w

What it means

ScanIssueCountsInTx runs a single-row aggregate SELECT (total/open/in_progress/closed/deferred/pinned counts) and scans six ints; this wraps any Scan failure. A single-row aggregate always returns one row, so failure usually means the row was NULL (empty/corrupt table), the query failed, or the destination types mismatch.

Source

Thrown at internal/storage/issueops/statistics.go:32

func ScanIssueCountsInTx(ctx context.Context, tx DBTX, stats *types.Statistics) error {
	if err := tx.QueryRowContext(ctx, `
		SELECT
			COUNT(*) AS total,
			COALESCE(SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END), 0),
			COALESCE(SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END), 0),
			COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0),
			COALESCE(SUM(CASE WHEN status = 'deferred' THEN 1 ELSE 0 END), 0),
			COALESCE(SUM(CASE WHEN pinned = 1 THEN 1 ELSE 0 END), 0)
		FROM issues
	`).Scan(
		&stats.TotalIssues,
		&stats.OpenIssues,
		&stats.InProgressIssues,
		&stats.ClosedIssues,
		&stats.DeferredIssues,
		&stats.PinnedIssues,
	); err != nil {
		return fmt.Errorf("scan issue counts: %w", err)
	}
	return nil
}

// GetStatisticsInTx computes the full summary statistics (counts + blocked + ready)
// in one transaction, using only the normal issues table — no version-control state —
// so it is portable across every SQL backend. Behaviorally identical to the Dolt and
// embedded-Dolt implementations: ScanIssueCountsInTx for the status counts, a direct
// blocked count (the is_blocked flag is maintained in-tx by the shared layer), then
// ReadyIssues = OpenIssues - BlockedIssues clamped at zero.
func GetStatisticsInTx(ctx context.Context, tx DBTX) (*types.Statistics, error) {
	stats := &types.Statistics{}
	if err := ScanIssueCountsInTx(ctx, tx, stats); err != nil {
		return nil, err
	}
	var blocked int
	if err := tx.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM issues

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error text for 'no such table' — run the schema initialization/migration before calling statistics.
  2. Verify the database connection is alive and the tx wasn't already rolled back.
  3. If COUNT(*) returning NULL is the cause, confirm the issues table exists and has rows; an empty initialized table still returns one row of zeros, so NULL indicates schema corruption.
  4. Upgrade to a matching schema version if the error appeared after a beads version change.

Example fix

// before
stats, err := GetStatisticsInTx(ctx, tx) // fails: issues table missing
// after
if err := store.Initialize(ctx); err != nil { return err } // ensure schema
stats, err := GetStatisticsInTx(ctx, tx)
Defensive patterns

Strategy: validation

Validate before calling

// verify schema readiness before asking for statistics
rows, err := tx.QueryContext(ctx, `SHOW TABLES LIKE 'issues'`)
if err != nil || !rows.Next() { return fmt.Errorf("issues table missing; initialize database") }

Try / catch

stats, err := issueops.GetStatisticsInTx(ctx, tx)
if err != nil {
    if strings.Contains(err.Error(), "scan issue counts") {
        return fmt.Errorf("statistics unavailable; check schema and connection: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetStatisticsInTx (or ScanIssueCountsInTx directly) when the issues table doesn't exist, the connection is broken, the context is cancelled, or the database returns NULLs the int destinations can't absorb.

Common situations: Pointing bd at an empty or uninitialized database file; running stats before migrations created the issues table; a Dolt server outage while running `bd stats`; schema drift after a version upgrade.

Related errors


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