Skip to content

[Fix] Paired OpenCode idle events finalize runs while a drained follow-up turn is still running - #1157

Merged
daniel-lxs merged 1 commit into
developfrom
claude/nice-ptolemy-fcc1a7
Aug 7, 2026
Merged

[Fix] Paired OpenCode idle events finalize runs while a drained follow-up turn is still running#1157
daniel-lxs merged 1 commit into
developfrom
claude/nice-ptolemy-fcc1a7

Conversation

@daniel-lxs

Copy link
Copy Markdown
Member

Problem

OpenCode 1.17 emits session.status(idle) followed by a paired legacy session.idle for a single turn boundary. When a turn ends with a deliverable prompt queued (for example the hidden re-review follow-up that active PR reviews receive when commits are pushed mid-review, #1150), the status-sourced completion correctly defers run finalization and drains the queued prompt. But draining submits the prompt and re-arms inFlight, so the trailing paired session.idle re-entered finishCurrentTurn, emitted a second taskCompleted against the now-empty queue, and HarnessManager finalized the run (onExit) while the drained turn was still running.

Consequences:

  • The follow-up turn ran in a post-finalization window where only the sleep heartbeat kept the sandbox alive, so it could be lost to sleep.
  • The drained turn's real completion later fired a duplicate onExit.
  • Whether a live run hit this depended on whether OpenCode emitted the paired events for that boundary, which made the deferral look nondeterministic.

The existing lifecycle integration test missed this because its mock emitted only a single session.idle, never the paired sequence the real binary produces.

Fix

Arm a new ignoreNextQueuedDrainSessionIdle guard when a status-sourced turn completion's drain submits a queued prompt, mirroring the three existing paired-idle guards (stop-hook reminder, question replay, provider recovery). The trailing session.idle is swallowed exactly once; busy/retry status transitions and StartNewTask clear the flag like its siblings. Turns that end with only session.idle, empty-queue completions, and the other guards are unchanged.

Also includes the deferral decision inputs (phase, queued prompt count, deferred settlement) in the manager's task-completed log line so future bypasses are diagnosable from live logs.

Testing

  • New regression test active-review-follow-up-paired-idle.test.ts completes turns with the real paired sequence via the real harness + manager. It fails deterministically without the fix (premature onExit) and passes with it.
  • A/B smoke against a real opencode serve 1.17.8 binary with the real harness + manager, injecting a hidden follow-up mid-turn: pre-fix, the paired idle fired a second taskCompleted 16ms after the drain and onExit landed between the turns, followed by a third taskCompleted and duplicate onExit when the follow-up turn actually finished. With the fix: exactly one taskCompleted per turn and a single onExit after the follow-up turn settled.
  • All 257 worker harness/harness-manager tests pass; tsc --noEmit clean.

…follow-up

OpenCode 1.17 emits session.status(idle) followed by a paired legacy
session.idle for a single turn boundary. When the status-sourced turn
completion drained a queued deliverable prompt (e.g. a hidden PR re-review
follow-up), submitPrompt re-armed inFlight, so the trailing session.idle
re-entered finishCurrentTurn, emitted a second taskCompleted against the
now-empty queue, and HarnessManager finalized the run (onExit) while the
drained turn was still running. The follow-up then ran in a post-finalization
window kept alive only by the sleep heartbeat, and the drained turn's real
completion later fired a duplicate onExit.

Fix: arm an ignoreNextQueuedDrainSessionIdle guard when a status-sourced
completion's drain submits a prompt, mirroring the existing paired-idle
guards for stop-hook reminders, question replays, and provider recovery.
Busy/retry status transitions and StartNewTask clear it like its siblings.

The existing lifecycle test missed this because its mock emitted only a
single session.idle; the new regression test completes turns with the real
paired sequence and fails deterministically without the fix. Also include
the deferral decision inputs (phase, queued prompt count, deferred
settlement) in the manager's task-completed log line so future bypasses are
diagnosable from live logs.
@roomote-community

roomote-community Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

No code issues found. See task

Reviewed 3f47ce7

@daniel-lxs
daniel-lxs merged commit 2ff1522 into develop Aug 7, 2026
19 checks passed
@daniel-lxs
daniel-lxs deleted the claude/nice-ptolemy-fcc1a7 branch August 7, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant