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
- Run SELECT name, url FROM dolt_remotes manually and check the column count/types
- Upgrade or align the Dolt engine version with what beads expects
- Scan into sql.RawBytes/sql.NullString if values can be NULL, then convert
- 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
- Keep the Dolt engine version aligned with the version beads was built against
- After engine upgrades, smoke-test SELECT name, url FROM dolt_remotes
- Treat unexpected column counts as an upgrade signal, not a data problem
- Avoid NULLs in dolt_remotes values (they are engine-managed, but verify after manual edits)
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
- iter issues: scan: %w
- list Dolt remotes before git-protocol routing for peer %q: %
- iter dependents: scan: %w
- scan comment: %w
- remote-migrate gate: read remotes: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f9695f7424b5630b.
Report an issue: GitHub.