From 2d36d95492077775d596fffc1eb9fb83857bea90 Mon Sep 17 00:00:00 2001 From: MayurK-cmd Date: Thu, 27 Aug 2026 20:28:37 +0530 Subject: [PATCH] fix: gracefully handle corrupted keypair files in keys list Previously, if any keypair file was corrupted or unreadable, the entire 'keys list' command would crash due to Promise.all() rejecting. Now uses Promise.allSettled() to load all valid keys and warn about failed ones. Changes: - Replace Promise.all() with Promise.allSettled() - Track failed keys and display warning message - Add e2e test verifying command succeeds with corrupted files present --- src/commands/KeysCommand.ts | 31 +++++++++++++++++++++++++++++-- src/e2e.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/commands/KeysCommand.ts b/src/commands/KeysCommand.ts index 3ba5522..66f8ba9 100644 --- a/src/commands/KeysCommand.ts +++ b/src/commands/KeysCommand.ts @@ -126,19 +126,40 @@ export class KeysCommand { ); const keychainFiles = files.filter((f) => f.endsWith(".keychain.json")); - const keypairData = await Promise.all( + const results = await Promise.allSettled( keypairFiles.map(async (file) => { const name = file.replace(".json", ""); const signer = await Signer.load(name); return { name, address: signer.address, - type: "keypair", + type: "keypair" as const, active: settings.activeKey === name, }; }) ); + interface KeypairEntry { + name: string; + address: string; + type: "keypair"; + active: boolean; + } + + const keypairData: KeypairEntry[] = []; + const failedKeys: string[] = []; + + for (let i = 0; i < results.length; i++) { + const r = results[i]!; + if (r.status === "fulfilled") { + keypairData.push(r.value); + } else { + failedKeys.push( + `${keypairFiles[i]!.replace(".json", "")} (${String(r.reason)})` + ); + } + } + const keychainData = keychainFiles.map((file) => { const name = file.replace(".keychain.json", ""); const config = KeychainConfig.load(name); @@ -152,6 +173,12 @@ export class KeysCommand { const data = [...keypairData, ...keychainData]; + if (failedKeys.length > 0) { + console.warn( + `Warning: Failed to load ${failedKeys.length} key(s):\n${failedKeys.map((k) => ` - ${k}`).join("\n")}` + ); + } + if (Output.isJson()) { Output.json(data); return; diff --git a/src/e2e.test.ts b/src/e2e.test.ts index fc3f878..4b13c3e 100644 --- a/src/e2e.test.ts +++ b/src/e2e.test.ts @@ -105,4 +105,29 @@ describe("keys add (dist/index.js)", () => { expect(second.status).not.toBe(0); expect(second.stdout + second.stderr).toContain("already exists"); }); + + test("gracefully handles corrupted keypair files in keys list", () => { + // Create a corrupted keypair file directly + const keysDir = require("node:path").join( + tmpHome, + ".config", + "jup", + "keys" + ); + require("node:fs").mkdirSync(keysDir, { recursive: true }); + require("node:fs").writeFileSync( + require("node:path").join(keysDir, "valid-key.json"), + JSON.stringify([1, 2, 3, 4, 5]) + ); + require("node:fs").writeFileSync( + require("node:path").join(keysDir, "corrupted.json"), + "not valid json {" + ); + + // List should succeed without crashing (exit 0), even with corrupted file + const result = runCli("keys", "list"); + expect(result.status).toBe(0); + // Should show the valid key in output + expect(result.stdout).toContain("valid-key"); + }); });