From 846d655706b4328ac48d0f78e0fc096a1342dfda Mon Sep 17 00:00:00 2001 From: L <6723574+louisgv@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:47:21 -0500 Subject: [PATCH] perf: fetch SSH key list once before loop in ensureSshKey() (#2194) Both Hetzner and DigitalOcean were calling GET /ssh_keys inside the per-key loop, causing N redundant API round-trips when a user had multiple local SSH keys. Move the fetch outside the loop so it runs exactly once regardless of how many keys are being registered. Co-authored-by: Claude Co-authored-by: Claude Sonnet 4.6 --- packages/cli/src/digitalocean/digitalocean.ts | 10 +++++----- packages/cli/src/hetzner/hetzner.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/digitalocean/digitalocean.ts b/packages/cli/src/digitalocean/digitalocean.ts index c6a857cf..be8e024f 100644 --- a/packages/cli/src/digitalocean/digitalocean.ts +++ b/packages/cli/src/digitalocean/digitalocean.ts @@ -570,6 +570,11 @@ export async function ensureDoToken(): Promise { export async function ensureSshKey(): Promise { const selectedKeys = await ensureSshKeys(); + // Fetch registered keys once before the loop to avoid N+1 API calls + const keysText = await doApi("GET", "/account/keys"); + const data = parseJsonObj(keysText); + const keys = toObjectArray(data?.ssh_keys); + for (const key of selectedKeys) { const fingerprint = getSshFingerprint(key.pubPath); if (!fingerprint) { @@ -577,11 +582,6 @@ export async function ensureSshKey(): Promise { continue; } - // Check if key is registered with DigitalOcean - const keysText = await doApi("GET", "/account/keys"); - const data = parseJsonObj(keysText); - const keys = toObjectArray(data?.ssh_keys); - const found = keys.some((k: Record) => { const fp = k.fingerprint || ""; return fp === fingerprint; diff --git a/packages/cli/src/hetzner/hetzner.ts b/packages/cli/src/hetzner/hetzner.ts index 476fdad7..a1ee624f 100644 --- a/packages/cli/src/hetzner/hetzner.ts +++ b/packages/cli/src/hetzner/hetzner.ts @@ -182,15 +182,15 @@ export async function ensureHcloudToken(): Promise { export async function ensureSshKey(): Promise { const selectedKeys = await ensureSshKeys(); + // Fetch registered keys once before the loop to avoid N+1 API calls + const resp = await hetznerApi("GET", "/ssh_keys"); + const data = parseJsonObj(resp); + const sshKeys = toObjectArray(data?.ssh_keys); + for (const key of selectedKeys) { const fingerprint = getSshFingerprint(key.pubPath); const pubKey = readFileSync(key.pubPath, "utf-8").trim(); - // Check if key is already registered - const resp = await hetznerApi("GET", "/ssh_keys"); - const data = parseJsonObj(resp); - const sshKeys = toObjectArray(data?.ssh_keys); - const alreadyRegistered = sshKeys.some((k) => fingerprint && k.fingerprint === fingerprint); if (alreadyRegistered) {