From 4b67092c2b5ffd104eefb5f2258be84cdef36d8f Mon Sep 17 00:00:00 2001 From: zeroaltitude Date: Tue, 3 Mar 2026 09:45:57 -0700 Subject: [PATCH] fix(channels): skip default account injection when named accounts inherit base tokens - Don't inject 'default' when all named accounts lack per-account auth overrides (they'd use the same base credentials, causing duplicate provider connections and event processing) - Use normalizeAccountId for case-insensitive default detection - Applies to Slack (botToken/appToken) and Discord (token) alike Addresses review feedback from codex-connector on PR #30310. --- src/channels/plugins/account-helpers.test.ts | 41 +++++++++++++++++++- src/channels/plugins/account-helpers.ts | 26 ++++++++++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/channels/plugins/account-helpers.test.ts b/src/channels/plugins/account-helpers.test.ts index b82625dd0c2..82a4122c178 100644 --- a/src/channels/plugins/account-helpers.test.ts +++ b/src/channels/plugins/account-helpers.test.ts @@ -82,7 +82,20 @@ describe("createAccountListHelpers", () => { expect(listAccountIds(cfg({ z: {}, a: {}, m: {} }))).toEqual(["a", "m", "z"]); }); - it("includes default when base config has botToken and named accounts exist", () => { + it("includes default when base has tokens AND a named account has its own tokens", () => { + const config = { + channels: { + testchannel: { + botToken: "xoxb-base", + appToken: "xapp-base", + accounts: { tank: { botToken: "xoxb-tank" } }, + }, + }, + } as unknown as OpenClawConfig; + expect(listAccountIds(config)).toEqual(["default", "tank"]); + }); + + it("does NOT inject default when named accounts inherit base tokens (avoids duplicates)", () => { const config = { channels: { testchannel: { @@ -92,7 +105,31 @@ describe("createAccountListHelpers", () => { }, }, } as unknown as OpenClawConfig; - expect(listAccountIds(config)).toEqual(["default", "tank"]); + expect(listAccountIds(config)).toEqual(["tank"]); + }); + + it("does NOT inject default when Discord named accounts inherit base token", () => { + const config = { + channels: { + testchannel: { + token: "discord-bot-token", + accounts: { teamA: {} }, + }, + }, + } as unknown as OpenClawConfig; + expect(listAccountIds(config)).toEqual(["teamA"]); + }); + + it("does not duplicate default when already in accounts (case-insensitive)", () => { + const config = { + channels: { + testchannel: { + botToken: "xoxb-base", + accounts: { Default: {}, tank: {} }, + }, + }, + } as unknown as OpenClawConfig; + expect(listAccountIds(config)).toEqual(["Default", "tank"]); }); it("does not duplicate default when already in accounts", () => { diff --git a/src/channels/plugins/account-helpers.ts b/src/channels/plugins/account-helpers.ts index 03fbf9a9725..48297ae0f83 100644 --- a/src/channels/plugins/account-helpers.ts +++ b/src/channels/plugins/account-helpers.ts @@ -43,15 +43,29 @@ export function createAccountListHelpers( if (ids.length === 0) { return [DEFAULT_ACCOUNT_ID]; } + // Check whether any existing named account already normalizes to "default". + const normalizedIds = ids.map(normalizeAccountId); + if (normalizedIds.includes(DEFAULT_ACCOUNT_ID)) { + return ids.toSorted((a, b) => a.localeCompare(b)); + } // If the base channel config has its own tokens (botToken/appToken/token), - // include the default account alongside named accounts so both providers start. - if (!ids.includes(DEFAULT_ACCOUNT_ID)) { - const channel = cfg.channels?.[channelKey]; - const base = channel as Record | undefined; - const hasBaseTokens = Boolean(base?.botToken || base?.appToken || base?.token); - if (hasBaseTokens) { + // only inject a default account when at least one named account carries its + // own per-account auth. When every named account inherits the base tokens + // (i.e. has no per-account botToken/appToken/token override), injecting + // default would start a duplicate provider on the same credentials. + const channel = cfg.channels?.[channelKey]; + const base = channel as Record | undefined; + const hasBaseTokens = Boolean(base?.botToken || base?.appToken || base?.token); + if (hasBaseTokens) { + const accounts = (base?.accounts ?? {}) as Record>; + const someAccountHasOwnTokens = ids.some((id) => { + const acct = accounts[id]; + return acct && Boolean(acct.botToken || acct.appToken || acct.token); + }); + if (someAccountHasOwnTokens) { return [DEFAULT_ACCOUNT_ID, ...ids].toSorted((a, b) => a.localeCompare(b)); } + // All named accounts inherit base tokens — don't inject default. } return ids.toSorted((a, b) => a.localeCompare(b)); }