diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index 2349785c9e1..01c0ae6cd98 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -919,6 +919,7 @@ Entries without `account` stay shared across all Matrix accounts, and entries wi Partial shared auth defaults do not create a separate implicit default account by themselves. OpenClaw only synthesizes the top-level `default` account when that default has fresh auth (`homeserver` plus `accessToken`, or `homeserver` plus `userId` and `password`); named accounts can still stay discoverable from `homeserver` plus `userId` when cached credentials satisfy auth later. If Matrix already has exactly one named account, or `defaultAccount` points at an existing named account key, single-account-to-multi-account repair/setup promotion preserves that account instead of creating a fresh `accounts.default` entry. Only Matrix auth/bootstrap keys move into that promoted account; shared delivery-policy keys stay at the top level. Set `defaultAccount` when you want OpenClaw to prefer one named Matrix account for implicit routing, probing, and CLI operations. +If multiple Matrix accounts are configured and one account id is `default`, OpenClaw uses that account implicitly even when `defaultAccount` is unset. If you configure multiple named accounts, set `defaultAccount` or pass `--account ` for CLI commands that rely on implicit account selection. Pass `--account ` to `openclaw matrix verify ...` and `openclaw matrix devices ...` when you want to override that implicit selection for one command. diff --git a/extensions/matrix/src/account-selection.test.ts b/extensions/matrix/src/account-selection.test.ts index 785e8139e65..b963cc6551b 100644 --- a/extensions/matrix/src/account-selection.test.ts +++ b/extensions/matrix/src/account-selection.test.ts @@ -56,6 +56,22 @@ describe("matrix account selection", () => { expect(requiresExplicitMatrixDefaultAccount(cfg)).toBe(true); }); + it('uses a named "default" Matrix account when defaultAccount is unset', () => { + const cfg: OpenClawConfig = { + channels: { + matrix: { + accounts: { + default: { homeserver: "https://matrix.example.org" }, + ops: { homeserver: "https://matrix.example.org" }, + }, + }, + }, + }; + + expect(resolveMatrixDefaultOrOnlyAccountId(cfg)).toBe("default"); + expect(requiresExplicitMatrixDefaultAccount(cfg)).toBe(false); + }); + it("finds the raw Matrix account entry by normalized account id", () => { const cfg: OpenClawConfig = { channels: { @@ -93,7 +109,7 @@ describe("matrix account selection", () => { expect(requiresExplicitMatrixDefaultAccount(cfg, env)).toBe(false); }); - it("treats mixed default and named env-backed Matrix accounts as multi-account", () => { + it('uses the "default" Matrix account when mixed default and named env-backed accounts exist', () => { const keys = getMatrixScopedEnvVarNames("team-ops"); const cfg: OpenClawConfig = { channels: { @@ -108,7 +124,8 @@ describe("matrix account selection", () => { } satisfies NodeJS.ProcessEnv; expect(resolveConfiguredMatrixAccountIds(cfg, env)).toEqual(["default", "team-ops"]); - expect(requiresExplicitMatrixDefaultAccount(cfg, env)).toBe(true); + expect(resolveMatrixDefaultOrOnlyAccountId(cfg, env)).toBe("default"); + expect(requiresExplicitMatrixDefaultAccount(cfg, env)).toBe(false); }); it("discovers default Matrix accounts backed only by global env vars", () => { diff --git a/extensions/matrix/src/account-selection.ts b/extensions/matrix/src/account-selection.ts index acde1d72a36..8f32cadb952 100644 --- a/extensions/matrix/src/account-selection.ts +++ b/extensions/matrix/src/account-selection.ts @@ -213,6 +213,9 @@ export function requiresExplicitMatrixDefaultAccount( if (configuredAccountIds.length <= 1) { return false; } + if (configuredAccountIds.includes(DEFAULT_ACCOUNT_ID)) { + return false; + } const configuredDefault = normalizeOptionalAccountId( typeof channel.defaultAccount === "string" ? channel.defaultAccount : undefined, ); diff --git a/extensions/matrix/src/matrix/client.test.ts b/extensions/matrix/src/matrix/client.test.ts index a27362d2b6a..fd48f89b01e 100644 --- a/extensions/matrix/src/matrix/client.test.ts +++ b/extensions/matrix/src/matrix/client.test.ts @@ -403,6 +403,29 @@ describe("Matrix auth/config live surfaces", () => { ); }); + it('uses a named "default" account implicitly when multiple Matrix accounts exist', () => { + const cfg = { + channels: { + matrix: { + accounts: { + default: { + homeserver: "https://matrix.default.example.org", + accessToken: "default-token", + }, + ops: { + homeserver: "https://matrix.ops.example.org", + accessToken: "ops-token", + }, + }, + }, + }, + } as CoreConfig; + + expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe( + "default", + ); + }); + it("does not materialize a default account from shared top-level defaults alone", () => { const cfg = { channels: { @@ -439,7 +462,7 @@ describe("Matrix auth/config live surfaces", () => { expect(resolveMatrixAuthContext({ cfg, env: {} as NodeJS.ProcessEnv }).accountId).toBe("ops"); }); - it("honors injected env when implicit Matrix account selection becomes ambiguous", () => { + it('uses the injected env-backed "default" Matrix account when implicit selection is available', () => { const cfg = { channels: { matrix: {}, @@ -452,9 +475,7 @@ describe("Matrix auth/config live surfaces", () => { MATRIX_OPS_ACCESS_TOKEN: "ops-token", } as NodeJS.ProcessEnv; - expect(() => resolveMatrixAuthContext({ cfg, env })).toThrow( - /channels\.matrix\.defaultAccount.*--account /i, - ); + expect(resolveMatrixAuthContext({ cfg, env }).accountId).toBe("default"); }); it("does not materialize a default env account from partial global auth fields", () => {