fix(matrix): fix accountId not forwarded in sendMessage tool action#28399
fix(matrix): fix accountId not forwarded in sendMessage tool action#28399psacc wants to merge 2 commits into
Conversation
Greptile SummaryThis PR fixes a critical bug in multi-account Matrix setups where the The fix is minimal and surgical:
The implementation maintains backward compatibility by using the Confidence Score: 5/5
Last reviewed commit: 439da7e |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 439da7e39e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -82,10 +82,12 @@ export async function handleMatrixAction( | |||
| const replyToId = | |||
| readStringParam(params, "replyToId") ?? readStringParam(params, "replyTo"); | |||
| const threadId = readStringParam(params, "threadId"); | |||
| const accountId = readStringParam(params, "accountId"); | |||
There was a problem hiding this comment.
Forward accountId from the send adapter path
handleMatrixAction now reads accountId from params, but the normal Matrix send flow still drops that field before this point: extensions/matrix/src/actions.ts builds a new object for handleMatrixAction with to/content/mediaUrl/replyToId/threadId only. In tool-driven sends (action: "send"), accountId therefore remains undefined here and sendMatrixMessage still falls back to default/any active client in multi-account setups, so this fix does not actually resolve the misrouting scenario it targets.
Useful? React with 👍 / 👎.
07d4dad to
50e5186
Compare
In multi-account Matrix setups, all tool actions from isolated cron and
subagent sessions were routing through getAnyActiveMatrixClient() —
returning the first account by Map insertion order, non-deterministic
across gateway restarts — instead of the agent's bound account. This
caused M_FORBIDDEN errors when the wrong account tried to send to a
room it was not a member of, with a false-positive deliveryStatus of
"delivered".
Three hops fixed:
1. actions.ts — matrixMessageActions.handleAction received ctx.accountId
(server-injected binding account) but never forwarded it when building
params for handleMatrixAction. Discord's equivalent adapter already had
the correct pattern; Matrix was missing it.
2. tool-actions.ts — accountId was not extracted from params for
editMessage, deleteMessage, readMessages, react, reactions, pinMessage,
unpinMessage, listPins. Only sendMessage had it.
3. actions/messages.ts — sendMatrixMessage accepted opts.accountId but
did not forward it to sendMessageMatrix.
Also fixes a reactMatrixMessage signature inconsistency (bare client?
param replaced with opts: {client?, accountId?}) and a broken call site
in monitor/handler.ts that would have sent reaction acknowledgements from
the wrong account. Adds structured send logging to sendMessageMatrix
([matrix:send] / [matrix:send:ok] / [matrix:send:fail]) and a warn to
getAnyActiveMatrixClient when called with multiple active accounts.
Closes openclaw#26457
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…w#26457) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63f3680 to
0ef1d4a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ef1d4a9b3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| action: "memberInfo", | ||
| userId, | ||
| roomId: readStringParam(params, "roomId") ?? readStringParam(params, "channelId"), | ||
| accountId: accountId ?? undefined, |
There was a problem hiding this comment.
Propagate accountId for member/channel info lookups
matrixMessageActions.handleAction now adds accountId for member-info/channel-info, but handleMatrixAction still calls getMatrixMemberInfo and getMatrixRoomInfo without passing that field (extensions/matrix/src/tool-actions.ts), so multi-account sessions can still resolve the wrong Matrix client and return unauthorized/incorrect room/member metadata. This leaves those actions with the same misrouting behavior the commit is fixing for message/reaction/pin actions.
Useful? React with 👍 / 👎.
|
This pull request has been automatically marked as stale due to inactivity. |
|
OpenClaw now uses a new Matrix plugin built on the official Closing this as potentially fixed in the new plugin. If this still reproduces after migrating, please open a new issue/PR against the new Matrix plugin and link back here. Migration/docs: |
Summary
messagetool dropsaccountIdat three hops before it reachesresolveMatrixClient. The field isaccepted by the tool schema but silently lost in the adapter chain.
getAnyActiveMatrixClient(), which returns the first account by Map insertion order —non-deterministic across restarts. In practice: wrong account sends the message,
M_FORBIDDENif the room is account-restricted, silent false-positivedeliveryStatus: "delivered"even when the message was dropped.All Matrix tool actions now correctly route through the agent's bound account.
Also fixes a
reactMatrixMessagecall site inmonitor/handler.ts, adds structuredsend logging to
sendMessageMatrix, and adds a warn togetAnyActiveMatrixClientwhen called with multiple active accounts.
gateways (
accountIdundefined → sameresolveMatrixClientfallback as before).Three hops fixed
Hop 1 —
actions.ts(adapter layer, root cause)matrixMessageActions.handleActionreceivesctx.accountId(server-injected bindingaccount for the current session) but never included it when building params for
handleMatrixAction. Discord's equivalent adapter already had the correct pattern:ctx.accountId ?? readStringParam(params, "accountId"). Matrix was missing it entirely.Hop 2 —
tool-actions.ts(dispatch layer)accountIdwas not extracted from params foreditMessage,deleteMessage,readMessages,react,reactions,pinMessage,unpinMessage,listPins.Only
sendMessagehad it (fixed in the first commit on this branch).Hop 3 —
actions/messages.ts(send layer)sendMatrixMessageacceptedopts.accountIdbut did not forward it tosendMessageMatrix.Change Type
Scope
Linked Issue/PR
User-visible / Behavior Changes
In multi-account Matrix setups, all
messagetool actions from isolated cron/subagentsessions now route to the account specified by
accountId(or the agent's boundaccount via
ctx.accountId) instead of an arbitrary active account.Security Impact
Repro + Verification
Environment
sessionTarget: "isolated", agent with a specificbound Matrix account; target room accessible only to that agent's account
Steps
only that agent's account is a member
messagetool routes to an arbitrary account, returnsM_FORBIDDENExpected
Message sent from the agent's own Matrix account. No
M_FORBIDDEN.Actual (before fix)
Sent from the first account in
activeClientsMap by startup order (non-deterministic).M_FORBIDDENif that account is not a member of the target room.Evidence
Before — wrong account used (Map insertion order), not in target room:
After — fix applied, different account still first in Map at startup:
Correct account used regardless of startup order. ✅
Human Verification
startup first; message still routed to the correct agent account.
sendMessage,editMessage,deleteMessage,readMessages,react,reactions,pinMessage,unpinMessage,listPins)now forward
accountIdcorrectly.accountIdisundefined, falls through toexisting
resolveMatrixClientdefault logic unchanged.Compatibility / Migration
Failure Recovery
actions.ts,tool-actions.ts,matrix/actions/messages.tsM_FORBIDDENfrom multi-account isolated sessionsRisks and Mitigations
getAnyActiveMatrixClient()as an implicitdefault (no
accountIdin params) now go throughresolveMatrixClient(undefined)instead.
resolveMatrixClient(undefined)already has its own fallback chain —behaviour is identical for callers that omit
accountId. Single-account setups:no change.