ACP: recover hung bound turns#51816
Conversation
Greptile SummaryThis PR adds a manager-side watchdog to Key observations:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/acp/control-plane/manager.core.ts
Line: 726-728
Comment:
**Dead `streamError` re-throw after `awaitTurnWithTimeout`**
After the refactoring, the `if (streamError) { throw streamError; }` check on this line is unreachable and can be removed:
- **Normal path** (`eventGate.open` remains `true`): If the for-await loop emits an error event, the IIFE throws `streamError` internally — that rejection propagates out of `awaitTurnWithTimeout` before this line is ever reached.
- **Timeout path** (`eventGate.open = false`): `awaitTurnWithTimeout` itself throws `AcpRuntimeError("ACP_TURN_FAILED", ...)`, so control never reaches this check.
In neither case can `awaitTurnWithTimeout` return without throwing while `streamError` is truthy.
```suggestion
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "ACP: recover hung bo..." |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8facdd077
ℹ️ 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".
| void Promise.allSettled([params.activeTurn.cancelPromise, closePromise]).then(() => { | ||
| this.clearCachedRuntimeStateIfHandleMatches({ | ||
| sessionKey: params.sessionKey, | ||
| handle: params.activeTurn.handle, | ||
| }); |
There was a problem hiding this comment.
Clear oneshot cache immediately after successful timeout close
In the oneshot timeout path, if cancel exceeds the grace period but close succeeds, this branch defers cache eviction until Promise.allSettled([cancelPromise, closePromise]) resolves; if cancelPromise never settles, eviction never happens even though the runtime session is already closed. That leaves a dead handle permanently counted in runtimeCache, which can exhaust maxConcurrentSessions and block new sessions after a single hung cancel call.
Useful? React with 👍 / 👎.
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]> (cherry picked from commit 8cac327)
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
* ACP: add hung-turn starvation repro * ACP: recover hung bound turns * ACP: preserve timed-out session handles --------- Co-authored-by: Onur <[email protected]>
Summary
runtime.runTurn()never completed, because later turns on that session stayed queued behind the stuck turn.#codex-1could look dead until the gateway or session was manually reset.AcpSessionManager.runTurn()now applies a manager-side watchdog, aborts/cancels/closes timed-out turns, drops the stale cached runtime handle, and lets queued turns proceed.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Security Impact (required)
No)No)No)No)No)Yes, explain risk + mitigation:Repro + Verification
Environment
pnpm testwrapperopenai-codex/gpt-5.4via acpx/Codex ACP session#codex-1)agent:codex:acp:binding:discord:default:f56b4624dd829241Steps
Expected
Actual
AcpSessionManager.runTurn()waited forever on the hung turn and kept the session actor occupied.Evidence
Attach at least one:
Evidence used:
src/acp/control-plane/manager.test.ts#codex-1LOOPER_BOT_TOKENto#codex-1now causes the bound ACP session state to return toidlepromptly and Bob emits a fresh reply message instead of leaving the worker hungHuman Verification (required)
What you personally verified (not just CI), and how:
pnpm test -- src/acp/control-plane/manager.test.tspnpm buildacpxprompt against the bound session returned visible text after the session was reset#codex-1viaLOOPER_BOT_TOKENproduced fresh Bob replies and advanced the bound ACP session back toidlesystemd --user) also round-trips after restarting on the built fixpnpm checkon this workstation is currently blocked by unrelated untracked plugin-sdk docs formatting work outside this PR diffReview Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes)No)No)Failure Recovery (if this breaks)
5cb9f5eb49src/acp/control-plane/manager.core.ts,src/acp/control-plane/manager.test.tsRisks and Mitigations