fix(gateway): seed subagent session deliveryContext from spawn request params#54479
fix(gateway): seed subagent session deliveryContext from spawn request params#54479mariosousa-finn wants to merge 3 commits into
Conversation
…t params
When a subagent is spawned via callGateway({method: 'agent', deliver: false}),
the gateway creates/updates the child session entry but only seeds
deliveryContext from the existing entry (which is null for new sessions).
The request's channel/to/threadId params — passed by the spawner to indicate
where announce results should be delivered — are not persisted.
This causes all subagent sessions to end up with:
deliveryContext: {channel: 'slack'}
missing the critical 'to' and 'threadId' fields.
When the subagent completes and the announce mechanism fires, it falls back to
resolveAnnounceOrigin → mergeDeliveryContext with the parent session's
lastTo/lastThreadId, which may have drifted if the parent handled other
messages. This produces three failure modes:
1. Correct delivery (parent entry unchanged — most common)
2. Wrong-channel delivery (parent lastTo/lastThreadId drifted)
3. Total delivery failure ('Outbound not configured for channel: slack')
Fix: merge the request's channel/to/accountId/threadId as a fallback into the
session entry's deliveryContext using the existing mergeDeliveryContext helper.
The existing entry's fields take priority (primary wins in merge), so this only
fills in missing values — it never overwrites an established delivery route.
Greptile SummaryThis PR fixes a real production regression on the Slack gateway where subagent sessions were spawned with an incomplete The fix is well-scoped: after computing Key observations:
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-methods/agent.ts
Line: 467-471
Comment:
**Numeric `threadId` silently dropped before `normalizeDeliveryContext`**
The guard `typeof request.threadId === "string" && request.threadId.trim()` causes any numeric `threadId` from request params to be replaced with `undefined` before reaching `normalizeDeliveryContext`. However, `normalizeDeliveryContext` already knows how to handle `number` threadIds — it truncates finite numbers via `Math.trunc`. Passing `request.threadId` directly would be both simpler and correct for all channel types (e.g., Matrix uses integer thread IDs).
Note: the same pre-existing pattern appears further down in this file at line 578, so this is consistent with existing code, but both sites could benefit from the simplification.
```suggestion
threadId: request.threadId,
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(gateway): seed subagent session deli..." | Re-trigger Greptile |
Address review feedback: normalizeDeliveryContext already handles both string and numeric threadIds (Math.trunc for finite numbers), so the typeof string guard was unnecessarily restrictive for channels that use integer thread IDs (e.g., Matrix).
|
Re: Greptile P2 — Numeric threadId silently dropped Good catch — fixed in 34da8ff. Passing |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
|
Confirming this fixes a real-world issue for us. We're on v2026.3.23-2 (upgraded from v2026.3.13 where everything worked) using openclaw agent --agent --deliver --channel telegram --reply-to --reply-account triggered from external scripts. Impact: This effectively breaks our multi-agent setup. We run 4 agents (main + 3 specialized agents with separate Telegram bots), all relying on --deliver for Telegram delivery. The primary --deliver path fails consistently since the update. The embedded fallback recovers intermittently, making delivery unreliable. Symptoms: Delivery failed (telegram to ): Error: Unknown channel: telegram / Outbound not configured for channel: telegram |
|
We have been having this error for a while in Slack. I can confirm that after this patch the error is gone. Thank you @mariosousa-finn |
…livery-context # Conflicts: # src/gateway/server-methods/agent.ts
|
Closing this as implemented after Codex review. Current What I checked:
So I’m closing this as already implemented rather than keeping a duplicate issue open. Review notes: reviewed against 8f64cd3e4d83; fix evidence: release v2026.4.22, commit 00bd2cf7a376. |
Summary
deliveryContext— only{"channel": "slack"}with notoorthreadId. When the subagent completes and the announce mechanism tries to deliver results back to the requester, it must reconstruct the delivery target from the parent session's mutablelastTo/lastThreadIdfields, which may have drifted if the parent handled other messages in the meantime.#temp_edgar_shelter)"Outbound not configured for channel: slack"when the channel plugin registry is unavailable at announce time (3 lost messages on 2026-03-25 alone)server-methods/agent.ts, thenextEntryPatchbuildsdeliveryContextexclusively fromnormalizeSessionDeliveryFields(entry), which reads from the existing session entry. For a freshly-spawned subagent (entry is null/empty), this returnsundefinedforto/threadId. The request'schannel/to/threadIdparams — passed byspawnSubagentDirectto indicate where announce results should go — are not persisted to the session store becausedeliver: falseskips theupdateLastRouteoutbound path.deliveryFieldsfrom the existing entry, we now merge in arequestDeliveryHintbuilt from the request'schannel/to/accountId/threadIdparams using the existingmergeDeliveryContexthelper. The existing entry's fields take priority (primary wins), so this only fills in missing values — it never overwrites an established delivery route.spawnSubagentDirect,registerSubagentRun, the announce delivery pipeline,resolveAnnounceOrigin,mergeDeliveryContext, ordeliveryContextFromSession. The fix is entirely in the gateway's agent request handler session entry initialization.deliveryContextthat forces the announce mechanism to rely on mutable parent session state in the first place. Both fixes are complementary.🤖 AI-assisted
Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Subagent sessions spawned via
sessions_spawnnow have a completedeliveryContext(includingtoandthreadId) frozen at spawn time. The announce delivery mechanism no longer needs to fall back to the parent session's mutablelastTo/lastThreadIdfields to determine where to route results.Security Impact (required)
NoNoNoNoNoRepro + Verification
Environment
Steps
Expected (after fix)
deliveryContext: {channel: "slack", to: "channel:C...", threadId: "original.thread.ts"}Actual (before fix)
deliveryContext: {channel: "slack"}— missingtoandthreadIdlastTo/lastThreadIdwhich now point to the different threadEvidence from production (2026-03-25)
Three subagent announces failed with
"Outbound not configured for channel: slack"at 09:17, 10:47, and 10:51 UTC. All three subagents haddeliveryContext: {"channel": "slack"}with notoorthreadId. The parent sessions had correct, complete delivery contexts.Test Plan
server.agent.subagent-delivery-context.test.ts— 3 new tests:to/threadId/accountIdare persistedmergeDeliveryContextprimary-wins semanticsserver.agent.gateway-server-agent-{a,b}tests pass