gastownhall/beads · error

scan remote: %w

Error message

scan remote: %w

What it means

ListRemotes scans each row of the dolt_remotes result into a storage.RemoteInfo via rows.Scan(&r.Name, &r.URL). This error is thrown when a row's columns cannot be converted into the two destination fields — most often a column-count or type mismatch between the result set and the scan targets. It identifies the failure as occurring mid-iteration, not at query time.

Source

Thrown at internal/storage/versioncontrolops/remotes.go:22

	"context"
	"fmt"

	"github.com/steveyegge/beads/internal/storage"
)

// ListRemotes returns all configured Dolt remotes (name and URL).
func ListRemotes(ctx context.Context, db DBConn) ([]storage.RemoteInfo, error) {
	rows, err := db.QueryContext(ctx, "SELECT name, url FROM dolt_remotes")
	if err != nil {
		return nil, fmt.Errorf("list remotes: %w", err)
	}
	defer rows.Close()

	var remotes []storage.RemoteInfo
	for rows.Next() {
		var r storage.RemoteInfo
		if err := rows.Scan(&r.Name, &r.URL); err != nil {
			return nil, fmt.Errorf("scan remote: %w", err)
		}
		remotes = append(remotes, r)
	}
	return remotes, rows.Err()
}

// RemoveRemote removes a configured Dolt remote.
func RemoveRemote(ctx context.Context, db DBConn, name string) error {
	if _, err := db.ExecContext(ctx, "CALL DOLT_REMOTE('remove', ?)", name); err != nil {
		return fmt.Errorf("remove remote %s: %w", name, err)
	}
	return nil
}

// Fetch fetches refs from a remote without merging.
//
// If user is non-empty, authenticates with that user — DOLT_REMOTE_PASSWORD
// must be set in the in-process Dolt server's environment.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run SELECT name, url FROM dolt_remotes manually and check the column count/types
  2. Upgrade or align the Dolt engine version with what beads expects
  3. Scan into sql.RawBytes/sql.NullString if values can be NULL, then convert
  4. Check for driver-specific type mapping issues in the installed dolt driver

Example fix

// before
if err := rows.Scan(&r.Name, &r.URL); err != nil {
	return nil, fmt.Errorf("scan remote: %w", err)
}
// after
var name, url sql.NullString
if err := rows.Scan(&name, &url); err != nil {
	return nil, fmt.Errorf("scan remote: %w", err)
}
r.Name, r.URL = name.String, url.String
Defensive patterns

Strategy: validation

Validate before calling

// Probe the dolt_remotes schema before scanning
rows, err := db.QueryContext(ctx, "SELECT name, url FROM dolt_remotes LIMIT 1")
if err != nil {
	return err
}
rows.Close()

Type guard

func isScanMismatch(err error) bool {
	var convErr *sql.ConvertError
	return errors.As(err, &convErr) || strings.Contains(err.Error(), "expected ")
}

Try / catch

remotes, err := versioncontrolops.ListRemotes(ctx, db)
if err != nil {
	if isScanMismatch(err) {
		log.Warn("dolt_remotes schema mismatch; upgrade Dolt engine")
	}
	return err
}

Prevention

When it happens

Trigger: Calling ListRemotes on a Dolt version whose dolt_remotes table has different column count/order than (name, url), or NULL/non-string values that database/sql cannot scan into *string fields.

Common situations: Dolt engine upgrade changed the dolt_remotes schema; connecting to a different storage engine where the query returns extra columns; driver returning typed values incompatible with string destinations.

Related errors


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