fix: enable ACP completion relay for main sessions#53256
Conversation
Greptile SummaryThis PR fixes a silent result-drop bug where The fix replaces the compound Key correctness points:
Both outstanding review concerns from previous threads are resolved in this iteration. Confidence Score: 4/5
Reviews (2): Last reviewed commit: "fix: use isSubagentSessionKey for legacy..." | Re-trigger Greptile |
| (!requesterIsSubagentSession || | ||
| (!requesterHasActiveSubagentBinding && | ||
| requesterHeartbeatEnabled && | ||
| requesterHeartbeatRelayRouteUsable)); |
There was a problem hiding this comment.
Legacy subagent session keys unintentionally get implicit relay
requesterIsSubagentSession is defined as Boolean(requesterParsedSession) && isSubagentSessionKey(parentSessionKey). requesterParsedSession is the result of parseAgentSessionKey, which returns null for legacy-format keys that start with subagent: but don't match the agent:<id>:<rest> pattern.
For such a legacy key, requesterIsSubagentSession evaluates to false, so !requesterIsSubagentSession becomes true, and the relay is now unconditionally enabled — changing the intentional "no relay" behavior that the existing test at line 862 ("does not implicitly stream for legacy subagent requester session keys") documents and asserts. That test will now fail.
If the intent is to apply the heartbeat-gated path to any session that semantically looks like a subagent (including legacy-format keys), the condition should call isSubagentSessionKey(parentSessionKey) directly rather than the compound requesterIsSubagentSession variable:
| (!requesterIsSubagentSession || | |
| (!requesterHasActiveSubagentBinding && | |
| requesterHeartbeatEnabled && | |
| requesterHeartbeatRelayRouteUsable)); | |
| (!isSubagentSessionKey(parentSessionKey) || | |
| (!requesterHasActiveSubagentBinding && | |
| requesterHeartbeatEnabled && | |
| requesterHeartbeatRelayRouteUsable)); |
This way:
- Main/non-subagent sessions get the relay unconditionally (the intended fix).
- New-format subagent keys preserve the heartbeat-gated path.
- Legacy
subagent:*keys also hit the heartbeat-gated path (keeping existing behavior).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 497-500
Comment:
**Legacy subagent session keys unintentionally get implicit relay**
`requesterIsSubagentSession` is defined as `Boolean(requesterParsedSession) && isSubagentSessionKey(parentSessionKey)`. `requesterParsedSession` is the result of `parseAgentSessionKey`, which returns `null` for legacy-format keys that start with `subagent:` but don't match the `agent:<id>:<rest>` pattern.
For such a legacy key, `requesterIsSubagentSession` evaluates to `false`, so `!requesterIsSubagentSession` becomes `true`, and the relay is now unconditionally enabled — changing the intentional "no relay" behavior that the existing test at line 862 ("does not implicitly stream for legacy subagent requester session keys") documents and asserts. That test will now fail.
If the intent is to apply the heartbeat-gated path to any session that semantically looks like a subagent (including legacy-format keys), the condition should call `isSubagentSessionKey(parentSessionKey)` directly rather than the compound `requesterIsSubagentSession` variable:
```suggestion
(!isSubagentSessionKey(parentSessionKey) ||
(!requesterHasActiveSubagentBinding &&
requesterHeartbeatEnabled &&
requesterHeartbeatRelayRouteUsable));
```
This way:
- Main/non-subagent sessions get the relay unconditionally (the intended fix).
- New-format subagent keys preserve the heartbeat-gated path.
- Legacy `subagent:*` keys also hit the heartbeat-gated path (keeping existing behavior).
How can I resolve this? If you propose a fix, please make it concise.| const implicitStreamToParent = | ||
| !streamToParentRequested && | ||
| spawnMode === "run" && | ||
| !requestThreadBinding && | ||
| requesterIsSubagentSession && | ||
| !requesterHasActiveSubagentBinding && | ||
| Boolean(parentSessionKey) && | ||
| !requesterHasThreadContext && | ||
| requesterHeartbeatEnabled && | ||
| requesterHeartbeatRelayRouteUsable; | ||
| // Subagent orchestrators with heartbeat relay use the optimised path; | ||
| // all other sessions (including main) always get the relay. | ||
| (!requesterIsSubagentSession || | ||
| (!requesterHasActiveSubagentBinding && | ||
| requesterHeartbeatEnabled && | ||
| requesterHeartbeatRelayRouteUsable)); |
There was a problem hiding this comment.
No regression test added for the fixed main-session relay path
Per the repo's bug-fix merge gate (CLAUDE.md), bug-fix PRs require a regression test when feasible. The PR does not add a test case verifying that a plain main session key now receives an implicit relay for mode=run spawns. Adding such a test would document the fix and guard against future regressions.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 489-500
Comment:
**No regression test added for the fixed main-session relay path**
Per the repo's bug-fix merge gate (CLAUDE.md), bug-fix PRs require a regression test when feasible. The PR does not add a test case verifying that a plain main session key now receives an implicit relay for `mode=run` spawns. Adding such a test would document the fix and guard against future regressions.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f21687933
ℹ️ 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".
| (!requesterIsSubagentSession || | ||
| (!requesterHasActiveSubagentBinding && | ||
| requesterHeartbeatEnabled && | ||
| requesterHeartbeatRelayRouteUsable)); |
There was a problem hiding this comment.
Gate implicit parent relay on session-local heartbeat safety
This condition now makes implicitStreamToParent true for any non-subagent requester, which bypasses the safety checks in hasSessionLocalHeartbeatRelayRoute that were preventing non-local delivery contexts (for example session.scope="global" or heartbeat to/accountId overrides). In those configs, a mode=run spawn from a main session can enqueue relay updates that heartbeat delivers to an unrelated shared/pinned destination, so ACP completion/progress from one requester can leak into another channel/session.
Useful? React with 👍 / 👎.
|
Thanks for the sharp review @greptile-apps — both points were valid. Fixed in 6cb66fb:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cb66fb93c
ℹ️ 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".
| (!isSubagentSessionKey(parentSessionKey) || | ||
| (!requesterHasActiveSubagentBinding && | ||
| requesterHeartbeatEnabled && | ||
| requesterHeartbeatRelayRouteUsable)); |
There was a problem hiding this comment.
Restore session-local guard for implicit parent relay
This new implicitStreamToParent condition now auto-enables relay for every non-subagent requester, which bypasses the hasSessionLocalHeartbeatRelayRoute safety gate that previously blocked non-local heartbeat delivery contexts (for example session.scope="global" or heartbeat to/accountId overrides). In those configurations, a mode="run" ACP spawn can relay completion/progress through shared or pinned heartbeat destinations rather than the spawning session, so updates from one requester can surface in unrelated conversations.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9cce22b591
ℹ️ 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".
| (!isSubagentSessionKey(parentSessionKey) || | ||
| (!requesterHasActiveSubagentBinding && | ||
| requesterHeartbeatEnabled && |
There was a problem hiding this comment.
Gate main-session implicit relay on session-local route safety
This branch makes implicitStreamToParent true for every non-subagent requester, bypassing the hasSessionLocalHeartbeatRelayRoute safety constraints that previously blocked non-local heartbeat delivery contexts. In configurations like session.scope="global" or heartbeat overrides with explicit to/accountId, ACP spawn relay events are still enqueued and heartbeat wake-up can route those progress/completion updates to shared or pinned destinations, exposing one requester’s run status in unrelated conversations. Keep implicit relay behind the same session-local route checks (and require explicit streamTo="parent" when unsafe). Fresh evidence: the newly added non-subagent test in src/agents/acp-spawn.test.ts asserts this unconditional path, so the bypass is now intentional behavior.
Useful? React with 👍 / 👎.
Previously, implicitStreamToParent required requesterIsSubagentSession, which meant mode=run ACP spawns from main sessions (e.g. Discord-bound agent sessions) would fire off but never relay completion results back to the spawning session. Relax the guard to enable the relay for any session with a valid parentSessionKey. The original subagent-specific heuristic (heartbeat enabled + relay route usable) is preserved as a nested condition when the requester IS a subagent session. Fixes openclaw#52967 Related: openclaw#52878
…n regression test - Replace requesterIsSubagentSession with isSubagentSessionKey(parentSessionKey) to correctly handle legacy subagent:* keys (fixes test at line 862) - Add regression test: main (non-subagent) sessions get implicit relay for mode=run - Addresses Greptile review feedback on PR openclaw#53256
…reEach) Addresses Greptile nit — the test duplicated the mock setup already handled by beforeEach.
9cce22b to
d69dc35
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d69dc352b7
ℹ️ 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".
| Boolean(params.requester.parentSessionKey) && | ||
| !params.requester.hasThreadContext && | ||
| params.requester.heartbeatEnabled && | ||
| params.requester.heartbeatRelayRouteUsable; | ||
| // Subagent orchestrators with heartbeat relay use the optimised path; | ||
| // all other sessions (including main) always get the relay. | ||
| (!params.requester.isSubagentSession || |
There was a problem hiding this comment.
Reapply session-local guard for main implicit parent relay
This condition now enables implicitStreamToParent for every non-subagent requester with a parent session key, so the safety checks in hasSessionLocalHeartbeatRelayRoute (which reject session.scope="global" and heartbeat to/accountId overrides) are bypassed for main sessions. In those configurations, the spawned ACP relay emits system events that heartbeat can deliver to a shared or pinned destination instead of the spawning conversation, so progress/completion text from one session can surface in unrelated chats; this path is now codified by the new non-subagent implicit-stream test.
Useful? React with 👍 / 👎.
|
Closing as no longer current against The main-session symptom this PR targeted has drifted: current If there is still a main-session-specific completion gap on current main, please reopen with a fresh repro and we can evaluate it against the current relay/lifecycle branches (#40885 and #54701). |
|
Closing as no longer current against The main-session symptom this PR targeted has drifted: current If there is still a main-session-specific completion gap on current main, please reopen with a fresh repro and we can evaluate it against the current relay/lifecycle branches (#40885 and #54701). |
Problem
mode=runACP spawns from main sessions (e.g. Discord-bound agent sessions) fire successfully but never relay completion results back to the spawning session.The root cause:
implicitStreamToParentinresolveAcpSpawnStreamPlanrequiresrequesterIsSubagentSession, which isfalsefor main sessions. This means the completion relay is never set up, and the spawning session never receives the result.Fix
Relax the
implicitStreamToParentguard to enable the relay for any session with a validparentSessionKey, not just subagent sessions.The original subagent-specific heuristic (heartbeat enabled + relay route usable + no active binding) is preserved as a nested condition that only applies when the requester IS a subagent session. All other sessions (including main) get the relay unconditionally.
Uses
isSubagentSessionKey(parentSessionKey)instead of the compoundrequesterIsSubagentSessionflag to correctly handle legacysubagent:*key formats.Testing
mode=runspawn from main Discord session → CC runs → completion relays back ✅Community Impact
This is a widely reported issue across the ACP ecosystem. Our fix directly addresses or unblocks:
Directly fixes
Addresses the core problem described in
Unblocks / partially resolves
The
implicitStreamToParentgate being locked to subagent sessions is the root cause of the "ACP works but parent never hears back" pattern reported across Discord, Telegram, and other channel surfaces.Related