gastownhall/beads · error

failed to decrypt password: %w

Error message

failed to decrypt password: %w

What it means

GetFederationPeer loads a federation peer row from the federation_peers table and decrypts its stored password with the store's AES-256-GCM credential key. This error is returned when decryptPassword fails, meaning the ciphertext in password_encrypted cannot be authenticated/decrypted with the currently loaded key. The library throws it to prevent silently using a corrupted or wrong-key password for peer sync.

Source

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

	if err != nil {
		return nil, fmt.Errorf("failed to get 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)
		}
	}

	return &peer, nil
}

// ListFederationPeers returns all configured federation peers.
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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Regenerate the peer's credentials: delete the peer (bd's peer-remove path) and re-add it with AddFederationPeer so the password is re-encrypted with the current key.
  2. Check that .beads/.beads-credential-key exists, is 32 bytes, and matches the one used when the peer was added; restore the original key file from backup if available.
  3. Verify the ciphertext is intact (not truncated by a bad import/restore); re-import the peer row if corrupted.
  4. If the key cannot be recovered, remove all stored peer passwords and re-enter them.

Example fix

// before: peer rows hold ciphertext from an old/lost key
peer.Password, err = s.decryptPassword(encryptedPwd) // cipher: message authentication failed
// after: re-add the peer so the password is re-encrypted with the current key
_ = store.RemoveFederationPeer(ctx, "peer-name")
peer.Password = "correct-password"
err = store.AddFederationPeer(ctx, peer)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(filepath.Join(beadsDir, ".beads-credential-key")); err != nil {
    return fmt.Errorf("credential key missing; re-add peer passwords before syncing: %w", err)
}

Type guard

func isDecryptFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to decrypt password")
}

Try / catch

peer, err := store.GetFederationPeer(ctx, name)
if isDecryptFailure(err) {
    // re-add peer credentials to re-encrypt with current key
    _ = store.RemoveFederationPeer(ctx, name)
    err = store.AddFederationPeer(ctx, &storage.FederationPeer{Name: name, RemoteURL: url, Password: pwd})
}

Prevention

When it happens

Trigger: The peer row's password_encrypted was written with a different key than the one now in .beads/.beads-credential-key — e.g. the key file was deleted/regenerated, the workspace was copied without the key file, the DB was restored from backup while the key file is newer, or the ciphertext itself is truncated/corrupt (fails the GCM auth check, 'cipher: message authentication failed').

Common situations: Cloning or restoring .beads/dolt data from backup while keeping or regenerating the key file; syncing a workspace between machines without copying .beads-credential-key; manually editing or migrating the federation_peers table; a crash during key migration leaving stale ciphertext.

Related errors


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