gastownhall/beads · error

failed to migrate credential keys: %w

Error message

failed to migrate credential keys: %w

What it means

After generating a new random credential key, initCredentialKey calls migrateCredentialKeys to re-encrypt existing federation peer passwords from the old dbPath-derived (legacy) key to the new random key. This error wraps any failure of that migration — a failed row scan, iteration, re-encryption, or database UPDATE. The store aborts key initialization rather than leaving peer passwords in a mixed encryption scheme.

Source

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

		if oldErr == nil && len(oldKey) == 32 {
			// Write to new location, then remove old file
			if writeErr := os.WriteFile(keyPath, oldKey, 0600); writeErr == nil {
				_ = os.Remove(oldKeyPath)
			}
			s.credentialKey = oldKey
			return nil
		}
	}

	// Generate new random 32-byte key (AES-256)
	key = make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return fmt.Errorf("failed to generate credential encryption key: %w", err)
	}

	// Migrate existing credentials from old dbPath-derived key to new random key
	if err := s.migrateCredentialKeys(ctx, key); err != nil {
		return fmt.Errorf("failed to migrate credential keys: %w", err)
	}

	// Write key file with owner-only permissions (0600).
	// Ensure the directory exists first — when connecting to an external
	// server without having run `bd init`, .beads/ may not exist yet (GH#2641).
	if err := os.MkdirAll(s.beadsDir, 0700); err != nil {
		return fmt.Errorf("failed to create beads directory %s: %w", s.beadsDir, err)
	}
	if err := os.WriteFile(keyPath, key, 0600); err != nil {
		return fmt.Errorf("failed to write credential key file: %w", err)
	}

	s.credentialKey = key
	return nil
}

// ensureCredentialKey lazily initializes the credential key when federation
// operations actually need password encryption or decryption.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the operation — migration is only attempted when no usable key file exists; once it succeeds the key file is written and migration never runs again
  2. Verify the Dolt database is reachable and accepting writes (run a trivial bd command like `bd ready`)
  3. Check for concurrent bd processes or a dolt-sql-server holding locks on federation_peers and serialize access
  4. Inspect the wrapped inner error (%w chain) for the exact SQL failure and address that root cause
Defensive patterns

Strategy: retry

Validate before calling

// Before upgrading, verify the DB is writable
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("dolt unavailable: %w", err) }

Try / catch

err := bdCmd()
if err != nil && strings.Contains(err.Error(), "failed to migrate credential keys") {
    // migration is idempotent while no key file exists — safe to retry
    return retryWithBackoff(bdCmd, 3)
}

Prevention

When it happens

Trigger: initCredentialKey runs with a valid database connection and existing rows in federation_peers whose password_encrypted decrypts with the legacy key, and migrateCredentialKeys then fails: rows.Scan error, rows.Err() iteration error, encryptWithKey failure, or the UPDATE federation_peers statement errors (e.g. context canceled, connection dropped, lock timeout).

Common situations: Upgrading bd from the legacy key scheme to random keys while the Dolt database is unavailable, locked by another writer, or the context times out mid-migration; stale server connections dropping during the UPDATE loop; concurrent bd processes contending over federation_peers during open.

Related errors


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