gastownhall/beads · error

failed to scan federation peer: %w

Error message

failed to scan federation peer: %w

What it means

ListFederationPeers scans each row into a FederationPeer with a fixed column list (name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at). This error means rows.Scan failed — the row's data doesn't match the expected 8-column shape or types. The library throws it to surface schema/driver mismatches during iteration.

Source

Thrown at internal/storage/dolt/credentials.go:382

func (s *DoltStore) ListFederationPeers(ctx context.Context) ([]*storage.FederationPeer, error) {
	rows, err := s.queryContext(ctx, `
		SELECT name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at
		FROM federation_peers ORDER BY name
	`)
	if err != nil {
		return nil, fmt.Errorf("failed to list federation peers: %w", err)
	}
	defer rows.Close()

	var peers []*storage.FederationPeer
	for rows.Next() {
		var peer storage.FederationPeer
		var encryptedPwd []byte
		var lastSync sql.NullTime
		var username sql.NullString

		if err := rows.Scan(&peer.Name, &peer.RemoteURL, &username, &encryptedPwd, &peer.Sovereignty, &lastSync, &peer.CreatedAt, &peer.UpdatedAt); err != nil {
			return nil, fmt.Errorf("failed to scan federation peer: %w", err)
		}

		if username.Valid {
			peer.Username = username.String
		}
		if lastSync.Valid {
			peer.LastSync = &lastSync.Time
		}

		// Decrypt password
		if len(encryptedPwd) > 0 {
			if err := s.ensureCredentialKey(ctx); err != nil {
				return nil, fmt.Errorf("failed to initialize credential key: %w", err)
			}
			peer.Password, err = s.decryptPassword(encryptedPwd)
			if err != nil {
				return nil, fmt.Errorf("failed to decrypt password: %w", err)
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run bd's schema migration/init to bring federation_peers to the current shape.
  2. Compare the table schema (SHOW CREATE TABLE federation_peers) against the 8 columns in the SELECT at credentials.go:366; add missing columns or fix types.
  3. If rows came from a manual import, re-import through AddFederationPeer instead.
  4. Check for a version mismatch between the bd binary and the database created by another version; align versions.

Example fix

// before: table missing last_sync column from old schema
err := rows.Scan(&peer.Name, &peer.RemoteURL, &username, &encryptedPwd, &peer.Sovereignty, &lastSync, &peer.CreatedAt, &peer.UpdatedAt) // scan error
// after: migrate schema so all 8 columns exist with compatible types
ALTER TABLE federation_peers ADD COLUMN last_sync DATETIME NULL;
Defensive patterns

Strategy: validation

Validate before calling

// verify expected columns before listing
rows, err := db.QueryContext(ctx, "SHOW COLUMNS FROM federation_peers")
// count columns; expect 8: name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at

Try / catch

peers, err := store.ListFederationPeers(ctx)
var scanErr bool
if err != nil && strings.Contains(err.Error(), "failed to scan federation peer") {
    scanErr = true // schema drift — run migrations
}

Prevention

When it happens

Trigger: The federation_peers table has a different column count or order than the SELECT expects (schema drift from a different beads version or manual ALTERs); a NOT-NULL column holds a value incompatible with the destination type; driver type-conversion failure on e.g. created_at.

Common situations: Upgrading or downgrading beads so the table schema no longer matches the compiled queries; a hand-edited or partially migrated Dolt database; restoring rows from an export with reordered columns.

Related errors


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