From d63966b0d85f21832b6817887696d4fb878dda12 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Fri, 17 Apr 2026 02:58:04 -0400 Subject: [PATCH] CLI: guard empty channel removal labels --- src/commands/configure.channels.test.ts | 33 +++++++++++++++++++++++++ src/commands/configure.channels.ts | 6 ++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/commands/configure.channels.test.ts b/src/commands/configure.channels.test.ts index 7653a3675be..db41eda7ff2 100644 --- a/src/commands/configure.channels.test.ts +++ b/src/commands/configure.channels.test.ts @@ -112,4 +112,37 @@ describe("removeChannelConfigWizard", () => { "Channel removed", ); }); + + it("uses a placeholder when an unknown channel key sanitizes to empty", async () => { + const unsafeChannel = "\u001B[31m\u0007"; + select.mockResolvedValueOnce(unsafeChannel).mockResolvedValueOnce("done"); + + const next = await removeChannelConfigWizard( + { + channels: { + [unsafeChannel]: { token: "secret" }, + telegram: { token: "secret" }, + }, + } as never, + {} as never, + ); + + expect(select).toHaveBeenCalledWith( + expect.objectContaining({ + options: expect.arrayContaining([ + expect.objectContaining({ value: unsafeChannel, label: "" }), + ]), + }), + ); + expect(confirm).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Delete configuration from ~/.openclaw/openclaw.json?", + }), + ); + expect(next.channels).toEqual({ telegram: { token: "secret" } }); + expect(note).toHaveBeenCalledWith( + " removed from config.\nNote: credentials/sessions on disk are unchanged.", + "Channel removed", + ); + }); }); diff --git a/src/commands/configure.channels.ts b/src/commands/configure.channels.ts index 4d5e5a0943f..20c6d05ca36 100644 --- a/src/commands/configure.channels.ts +++ b/src/commands/configure.channels.ts @@ -25,11 +25,15 @@ function listConfiguredChannelRemovalChoices( return Object.keys(channels) .map((id) => ({ id, - label: labelsById.get(id) ?? sanitizeTerminalText(id), + label: labelsById.get(id) ?? formatUnknownChannelRemovalLabel(id), })) .toSorted(compareChannelRemovalChoices); } +function formatUnknownChannelRemovalLabel(id: string): string { + return sanitizeTerminalText(id) || ""; +} + function compareChannelRemovalChoices( left: ConfiguredChannelRemovalChoice, right: ConfiguredChannelRemovalChoice,