fix: route subagent announce to originating parent session instead of channel-bound peer session#80242
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: yes. from source inspection and the linked issue: current main passes the sandbox/channel peer key into sessions_spawn ownership and checks the child binding before the requester binding. I did not run a live Telegram reproduction in this read-only review. Real behavior proof Next step before merge Security Review detailsBest possible solution: Land this routing fix after redacted real Telegram proof shows completion returns to the originating parent session while sandbox policy remains tied to the channel peer session. Do we have a high-confidence way to reproduce the issue? Yes from source inspection and the linked issue: current main passes the sandbox/channel peer key into sessions_spawn ownership and checks the child binding before the requester binding. I did not run a live Telegram reproduction in this read-only review. Is this the best way to solve the issue? Yes. Separating completion ownership from sandbox policy and preferring requester binding before child fallback is the narrow maintainable fix; the remaining blocker is proof, not a different code shape. Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 42435d110baa. |
a87beda to
99079b9
Compare
99079b9 to
45e8c13
Compare
|
Rebased on latest upstream/main (9e31c5f, clean rebase, no conflicts). |
45e8c13 to
8baabd5
Compare
|
Rebased on latest upstream/main (6c21472, clean rebase, no conflicts). |
8baabd5 to
1058390
Compare
|
Rebased on latest upstream/main (41859bb, clean rebase, no conflicts). |
1058390 to
7bf0226
Compare
|
Rebased on latest upstream/main (f885c39, clean rebase, no conflicts). |
7bf0226 to
a6d61c1
Compare
|
Rebased on latest upstream/main (5c7b203, clean rebase, no conflicts). |
|
Addressed P2 finding in fa8d742 — removed |
fa8d742 to
77e13b6
Compare
77e13b6 to
c770193
Compare
|
Rebased on latest upstream/main (e8a870c, clean rebase, no conflicts). |
c770193 to
1d612aa
Compare
|
Rebased on latest upstream/main (bb207ad, clean rebase, no conflicts). |
1d612aa to
b236e60
Compare
|
Rebased on latest upstream/main (384d74d, clean rebase, no conflicts). |
11653f1 to
54e5250
Compare
|
Rebased on latest upstream/main (7289e14, clean rebase, no conflicts). |
54e5250 to
7cd4f67
Compare
229f21b to
47a278c
Compare
|
Rebased on latest upstream/main (adac07f, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (65dd71d, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (192caba, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (777d289, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (a1d0b27). Resolved conflict in |
|
Rebased on latest upstream/main (c2e9091, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (9e67f53, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (ab2943e, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (a5b1177, clean rebase, no conflicts). |
|
Rebased on latest upstream/main (bf51933, clean rebase, no conflicts). |
… channel-bound peer session When a subagent is spawned from agent:main:main while a Telegram DM is active, the completion announce was delivered to the parallel Telegram channel session instead of the originating parent. Two interacting bugs: 1. The spawn tool received the sandbox/policy session key (Telegram peer key) as the requester, instead of the real run session key. Fixed by passing runSessionKey to createSessionsSpawnTool so the registered requester points to the actual parent session. 2. resolveSubagentCompletionOrigin checked child session bindings before requester bindings. When both share the same channel+accountId (common for Telegram DMs), the child binding hijacked the delivery target. Fixed by checking requester binding first, with child as fallback. Fixes openclaw#80201
The subagent_announce addition to AGENT_MEDIATED_COMPLETION_TOOLS was unrelated to the routing fix and could cause group/channel completions to fail silently when the subagent does not use the message tool. This should be addressed separately with proper message-tool-only guidance (tracked in openclaw#80223).
PR openclaw#80242 passed runSessionKey as agentSessionKey to createSessionsSpawnTool, which caused spawnSubagentDirect to use the run session key for sandbox policy checks (resolveSandboxRuntimeStatus). This could make a sandboxed channel run appear unsandboxed. Introduce completionOwnerKey as a separate field that is only used for registerSubagentRun routing (requesterSessionKey), keeping agentSessionKey for sandbox enforcement, callerDepth, activeChildren, and all other policy checks.
|
Maintainer fixup pushed and verified. Proof:
What changed:
Thanks @Jerry-Xin. |
|
Landed via squash merge.
Thanks @Jerry-Xin! |
Summary
When a subagent is spawned from
agent:main:mainwhile a Telegram DM session is active, the completion announce is delivered to the parallel channel-bound session (agent:main:telegram:default:direct:<chat_id>) instead of the originating parent session. The parent never observes the result and any logic gated on completion fails to fire.Root Cause
Two interacting bugs:
1. Spawn tool receives wrong requester key
The embedded-run pipeline passes
sandboxSessionKey(the Telegram peer key) through tocreateSessionsSpawnTool, which stores it asrequesterInternalKey:Result:
requesterInternalKey=agent:main:telegram:default:direct:<chat_id>instead ofagent:main:main.2. Child binding hijacks delivery target
resolveSubagentCompletionOrigininsubagent-announce-delivery.ts:319-335checked child session bindings before requester bindings. When both share the same channel + accountId (common for Telegram DMs with a single bot), the child binding won and routed the announce to the Telegram peer session.Fix
Change 1: Use
runSessionKeyfor spawn tool requester ownershipThe
runSessionKey(e.g.,agent:main:main) is already threaded through fromattempt.ts:1014and theCreateOpenClawToolsOptionsinterface. The??fallback preserves behavior when no separate run key exists.Change 2: Check requester binding first, child binding as fallback
This ensures the parent session's own binding (if any) determines where the announce is delivered, rather than being hijacked by the child's inherited binding.
Tests Added
sessions-spawn-tool.test.ts: VerifiesagentSessionKeyflows correctly tospawnSubagentDirectcontext, and that the Telegram peer key is not used when a run session key is available.subagent-announce-delivery.test.ts: Verifies requester binding takes priority when both child and requester share the same channel + accountId; verifies child binding still works as fallback when requester has no binding.Fixes #80201