fix(agents): register ACP runs in subagent registry for lifecycle tracking#46308
fix(agents): register ACP runs in subagent registry for lifecycle tracking#46308lanyasheng wants to merge 3 commits into
Conversation
…cking ACP sessions spawned via `sessions_spawn(runtime="acp")` were not registered in the subagent registry, making them invisible to the lifecycle event system. This meant `subagent_ended` hooks never fired for ACP sessions, preventing completion notifications from reaching the requester and leaving stale session records in `~/.acpx/sessions/index.json`. Add `registerSubagentRun()` call after successful ACP dispatch, mirroring the existing pattern in `subagent-spawn.ts`. This enables: - `subagent_ended` hook to fire when ACP sessions complete or exit - Proper cleanup of session state via the existing archive/reaper pipeline - Plugins (e.g. spawn-interceptor) to detect ACP completion through the standard event stream rather than relying on index.json polling Fixes openclaw#40272
Greptile SummaryThis PR adds a
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 740-745
Comment:
**Redundant `resolveMainSessionAlias` call**
`resolveMainSessionAlias(cfg)` is called here to obtain `mainKey` and `alias`, but these values are already derived at the very top of `spawnAcpDirect` inside `resolveRequesterInternalSessionKey` (line 413–416), which calls `resolveMainSessionAlias` internally. They are just not surfaced to the outer scope.
Consider either returning `mainKey`/`alias` from `resolveRequesterInternalSessionKey`, or calling `resolveMainSessionAlias(cfg)` once at the top of `spawnAcpDirect` (before calling `resolveRequesterInternalSessionKey`) so the values can be shared — matching the pattern in `subagent-spawn.ts` at line 319.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 746-757
Comment:
**Missing try/catch around `registerSubagentRun`**
The registration call is not wrapped in error handling. In `subagent-spawn.ts` (the pattern being mirrored at line 692–735), `registerSubagentRun` is wrapped in a `try/catch` that catches any thrown error and returns `{ status: "error", error: "Failed to register subagent run: ..." }` — preventing an unhandled exception from escaping the spawner function.
Without the same protection here, if `registerSubagentRun` throws (e.g. if `loadConfig()` fails inside it, or `ensureListener()` misbehaves), the exception will propagate out of `spawnAcpDirect` **even though the ACP session was already successfully dispatched** at lines 703–720. The caller would see an error response while the ACP session is actually live — creating exactly the class of orphaned/zombie sessions this PR aims to eliminate, just via a different code path.
Since the dispatch already succeeded, the right behavior on registration failure is to log a warning and continue returning the accepted response — not to throw:
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 5c35c02 |
| const { mainKey, alias } = resolveMainSessionAlias(cfg); | ||
| const requesterDisplayKey = resolveDisplaySessionKey({ | ||
| key: requesterInternalKey, | ||
| alias, | ||
| mainKey, | ||
| }); |
There was a problem hiding this comment.
Redundant resolveMainSessionAlias call
resolveMainSessionAlias(cfg) is called here to obtain mainKey and alias, but these values are already derived at the very top of spawnAcpDirect inside resolveRequesterInternalSessionKey (line 413–416), which calls resolveMainSessionAlias internally. They are just not surfaced to the outer scope.
Consider either returning mainKey/alias from resolveRequesterInternalSessionKey, or calling resolveMainSessionAlias(cfg) once at the top of spawnAcpDirect (before calling resolveRequesterInternalSessionKey) so the values can be shared — matching the pattern in subagent-spawn.ts at line 319.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 740-745
Comment:
**Redundant `resolveMainSessionAlias` call**
`resolveMainSessionAlias(cfg)` is called here to obtain `mainKey` and `alias`, but these values are already derived at the very top of `spawnAcpDirect` inside `resolveRequesterInternalSessionKey` (line 413–416), which calls `resolveMainSessionAlias` internally. They are just not surfaced to the outer scope.
Consider either returning `mainKey`/`alias` from `resolveRequesterInternalSessionKey`, or calling `resolveMainSessionAlias(cfg)` once at the top of `spawnAcpDirect` (before calling `resolveRequesterInternalSessionKey`) so the values can be shared — matching the pattern in `subagent-spawn.ts` at line 319.
How can I resolve this? If you propose a fix, please make it concise.| registerSubagentRun({ | ||
| runId: childRunId, | ||
| childSessionKey: sessionKey, | ||
| controllerSessionKey: requesterInternalKey, | ||
| requesterSessionKey: requesterInternalKey, | ||
| requesterOrigin, | ||
| requesterDisplayKey, | ||
| task: params.task, | ||
| cleanup: "keep", | ||
| label: params.label || undefined, | ||
| spawnMode, | ||
| }); |
There was a problem hiding this comment.
Missing try/catch around registerSubagentRun
The registration call is not wrapped in error handling. In subagent-spawn.ts (the pattern being mirrored at line 692–735), registerSubagentRun is wrapped in a try/catch that catches any thrown error and returns { status: "error", error: "Failed to register subagent run: ..." } — preventing an unhandled exception from escaping the spawner function.
Without the same protection here, if registerSubagentRun throws (e.g. if loadConfig() fails inside it, or ensureListener() misbehaves), the exception will propagate out of spawnAcpDirect even though the ACP session was already successfully dispatched at lines 703–720. The caller would see an error response while the ACP session is actually live — creating exactly the class of orphaned/zombie sessions this PR aims to eliminate, just via a different code path.
Since the dispatch already succeeded, the right behavior on registration failure is to log a warning and continue returning the accepted response — not to throw:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 746-757
Comment:
**Missing try/catch around `registerSubagentRun`**
The registration call is not wrapped in error handling. In `subagent-spawn.ts` (the pattern being mirrored at line 692–735), `registerSubagentRun` is wrapped in a `try/catch` that catches any thrown error and returns `{ status: "error", error: "Failed to register subagent run: ..." }` — preventing an unhandled exception from escaping the spawner function.
Without the same protection here, if `registerSubagentRun` throws (e.g. if `loadConfig()` fails inside it, or `ensureListener()` misbehaves), the exception will propagate out of `spawnAcpDirect` **even though the ACP session was already successfully dispatched** at lines 703–720. The caller would see an error response while the ACP session is actually live — creating exactly the class of orphaned/zombie sessions this PR aims to eliminate, just via a different code path.
Since the dispatch already succeeded, the right behavior on registration failure is to log a warning and continue returning the accepted response — not to throw:
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: 5c35c02005
ℹ️ 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".
| registerSubagentRun({ | ||
| runId: childRunId, | ||
| childSessionKey: sessionKey, | ||
| controllerSessionKey: requesterInternalKey, | ||
| requesterSessionKey: requesterInternalKey, |
There was a problem hiding this comment.
Tag ACP lifecycle runs with ACP target kind
This new registerSubagentRun(...) path records ACP spawns as generic subagent runs, but the completion hook emitter currently hard-codes targetKind: "subagent" (src/agents/subagent-registry-completion.ts:72). Since ACP events are now flowing through this registry path, plugins/hooks that rely on the supported ACP discriminator (src/agents/subagent-lifecycle-events.ts:1-6) cannot detect ACP completions correctly and will treat them as normal subagent endings.
Useful? React with 👍 / 👎.
…ainSessionAlias - Wrap registerSubagentRun() in try/catch so registration failure after successful dispatch does not propagate as error to caller (the ACP session is already live at that point) - Refactor resolveRequesterInternalSessionKey to return mainKey/alias, eliminating a redundant resolveMainSessionAlias(cfg) call
|
Thanks for the thorough review! Both issues addressed in f89aee6:
|
Add two tests to acp-spawn.test.ts: - Verify registerSubagentRun is called after successful ACP dispatch with correct runId, childSessionKey, task, and cleanup parameters. - Verify spawnAcpDirect still returns success when registerSubagentRun throws (the try/catch guard must not propagate registration failures).
BenliusYang
left a comment
There was a problem hiding this comment.
This PR looks exactly like what we need for the ACP completion relay issue #49782, please prioritize review.
BenliusYang
left a comment
There was a problem hiding this comment.
This PR looks exactly like what we need for the ACP completion relay issue #49782, please prioritize review.
|
Closing as duplicate of the ACP run-registration path now consolidated under #53125. This PR identified the same missing |
|
Closing as duplicate of the ACP run-registration path now consolidated under #53125. This PR identified the same missing |
Summary
ACP sessions spawned via sessions_spawn(runtime=acp) are never registered in the subagent registry (subagent-registry.ts), making them invisible to the lifecycle event system. This is the root cause of several issues:
Changes
Add a registerSubagentRun() call in acp-spawn.ts after successful ACP dispatch, mirroring the existing pattern in subagent-spawn.ts. This enables:
The registration uses cleanup: keep (ACP sessions manage their own workspace) and passes all available context (requesterOrigin, requesterDisplayKey, task, label, spawnMode) for full observability.
Test plan
Fixes #40272