Skip to content

fix(server): keep turn interrupts responsive - #6531

Open
NeilTheFisher wants to merge 1 commit into
pingdotgg:mainfrom
NeilTheFisher:fix/responsive-turn-interrupts
Open

fix(server): keep turn interrupts responsive#6531
NeilTheFisher wants to merge 1 commit into
pingdotgg:mainfrom
NeilTheFisher:fix/responsive-turn-interrupts

Conversation

@NeilTheFisher

@NeilTheFisher NeilTheFisher commented Aug 13, 2026

Copy link
Copy Markdown

Summary

  • settle OpenCode interrupts locally before waiting for the provider abort request
  • process interrupt commands on a dedicated worker so unrelated long-running commands cannot block Stop
  • treat OpenCode MessageAbortedError events as expected cancellation and keep the session ready
  • settle turn.aborted events in runtime ingestion

Validation

  • Live reproduction: Stop terminated a running test:ci command and settled backend state in under 100 ms
  • Focused server tests: 126 passed
  • Repository build: passed
  • Full server suite: 2,481 passed; 13 unrelated baseline failures (11 GitManager worktree tests, one newline-worktree-path test, and the existing ProviderRegistry reprobe test)

Note

Medium Risk
Changes orchestration turn lifecycle and OpenCode interrupt ordering; behavior is covered by new reactor, ingestion, and adapter tests but affects user-visible Stop semantics.

Overview
Stop no longer waits behind long-running provider work on the general command reactor queue: thread.turn-interrupt-requested events go to a dedicated interrupt worker, so a blocked session start on another thread cannot delay interrupt handling.

For OpenCode, interruptTurn now publishes turn.aborted and marks the session ready locally before calling session.abort. The remote abort runs best-effort in the background (errors ignored), so hung provider retries cannot block the UI transition. MessageAbortedError from session.error is treated as expected user cancellation (ready session, turn.aborted) instead of a provider error.

Runtime ingestion applies turn.aborted like a turn end: session returns to ready, active turn is cleared, and assistant messages are finalized the same path as completion.

Reviewed by Cursor Bugbot for commit c9d0bcb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix turn interrupts to process on a dedicated worker independently of blocked provider operations

  • Routes thread.turn-interrupt-requested events to a new dedicated interruptWorker in ProviderCommandReactor, preventing interrupts from queuing behind long-running start/stop/recovery tasks.
  • Reworks interruptTurn in OpenCodeAdapter to immediately settle the local session and emit turn.aborted before calling the remote abort, so the interrupt is responsive regardless of remote failures.
  • Treats session.error with MessageAbortedError as a successful interrupt: sets session status to ready, clears lastError, and emits turn.aborted instead of transitioning to an error state.
  • Handles turn.aborted in ProviderRuntimeIngestion by clearing activeTurnId, setting thread session to ready, and finalizing any assistant messages for the turn.
  • Behavioral Change: remote abort failures are now silently logged and ignored; callers of interruptTurn no longer receive abort errors.

Macroscope summarized c9d0bcb.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ba15257-ad95-4c2a-9509-30ee6c13d3ef

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 13, 2026
) {
return yield* worker.enqueue(event);
return yield* event.type === "thread.turn-interrupt-requested"
? interruptWorker.enqueue(event)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ProviderCommandReactor.ts:1418

A thread.turn-interrupt-requested can be processed before the same thread’s queued thread.turn-start-requested finishes, so providerService.interruptTurn runs before the provider session or turn exists; its failure is only logged, and the later turn starts without ever being interrupted. Coordinate the two lanes per thread while allowing interrupts for unrelated threads to bypass blocked work.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/Layers/ProviderCommandReactor.ts around line 1418:

A `thread.turn-interrupt-requested` can be processed before the same thread’s queued `thread.turn-start-requested` finishes, so `providerService.interruptTurn` runs before the provider session or turn exists; its failure is only logged, and the later turn starts without ever being interrupted. Coordinate the two lanes per thread while allowing interrupts for unrelated threads to bypass blocked work.

@NeilTheFisher
NeilTheFisher marked this pull request as draft August 13, 2026 20:57
Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
@macroscopeapp

macroscopeapp Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. Two High-severity findings identify race conditions where interrupts can process before turns start or where stale aborts can cancel subsequent turns. The changes also introduce new concurrency patterns in provider session management that warrant careful review.

You can customize Macroscope's approvability policy. Learn more.

@NeilTheFisher
NeilTheFisher force-pushed the fix/responsive-turn-interrupts branch from 9276f63 to c9d0bcb Compare August 13, 2026 21:23
@NeilTheFisher
NeilTheFisher marked this pull request as ready for review August 13, 2026 21:23
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Aug 13, 2026
context.activeTurnId = undefined;
context.activeAgent = undefined;
context.activeVariant = undefined;
yield* updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/OpenCodeAdapter.ts:1595

interruptTurn marks the session ready and clears activeTurnId before session.abort settles, so a new sendTurn can start on the same session while the old abort is pending; that abort can cancel the new turn. The later MessageAbortedError then reads the newer context.activeTurnId, clears it, and emits turn.aborted for the wrong turn. Prevent new prompts until the abort settles without delaying the local lifecycle update, or correlate the abort and error with the interrupted turn before mutating session state.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/OpenCodeAdapter.ts around line 1595:

`interruptTurn` marks the session `ready` and clears `activeTurnId` before `session.abort` settles, so a new `sendTurn` can start on the same session while the old abort is pending; that abort can cancel the new turn. The later `MessageAbortedError` then reads the newer `context.activeTurnId`, clears it, and emits `turn.aborted` for the wrong turn. Prevent new prompts until the abort settles without delaying the local lifecycle update, or correlate the abort and error with the interrupted turn before mutating session state.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c9d0bcb. Configure here.

Effect.mapError(toRequestError),
Effect.ignore({ log: true }),
Effect.forkIn(context.sessionScope),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale abort can cancel later turns

High Severity

interruptTurn now returns after a local turn.aborted and forks session.abort without binding it to that turn. A follow-up send can start while the abort is still in flight, so the remote abort or a later MessageAbortedError can cancel or settle the new turn instead of the stopped one.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c9d0bcb. Configure here.

: event.type === "turn.completed" || event.type === "session.exited"
: event.type === "turn.completed" ||
event.type === "turn.aborted" ||
event.type === "session.exited"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed prompts can look like interrupts

Medium Severity

Ingestion now maps every turn.aborted to session ready with lastError cleared. OpenCode already emits turn.aborted when a fresh promptAsync fails, so that failure can overwrite recoverTurnStartFailure and hide the error in orchestration session state.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c9d0bcb. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant