pingdotgg / pingdotgg/t3code

fix(server): Cursor adapter turn-lifecycle hardening (races, missing terminal events, lock retention)

Open
#9,047 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Summary

While building the omp ACP provider (#8985 / #9038, modeled on the Cursor driver) we verified three latent issues in apps/server/src/provider/Layers/CursorAdapter.ts. They are inherited patterns, not regressions — filing separately so they can be fixed on their own merits. The omp adapter shipped with the fixes; Cursor still has the originals.

1. sendTurn binds activeTurnId too late

ctx.promptsInFlight += 1 (CursorAdapter.ts ~L927) and ctx.activeTurnId = turnId (~L948, after effectful session configuration) leave an interleaving window: a second concurrent sendTurn reads the previous turn's activeTurnId and steers/attributes events onto the wrong turn. Fix (as in #9038): bind activeTurnId in the same synchronous stretch as the increment.

2. No terminal event when a started turn fails

turn.started is emitted before attachment reads and acp.prompt; failures from either propagate with no turn.completed, relying entirely on ProviderCommandReactor.handleTurnStartFailure to settle the session. RuntimeTurnState already supports "failed" with errorMessage — emitting the terminal event adapter-side (guarded on !ctx.stopped and the last-in-flight rule) makes the runtime event stream self-consistent.

3. Cancellation during turn preparation is ignored

interruptTurn only calls ctx.acp.cancel, which is a no-op before acp.prompt starts. A turn cancelled during applyRequestedSessionConfiguration / attachment reads still gets prompted. Fix (as in #9038): record the cancelled turn id in a session set, check it before turn.started and again before prompting, settle as cancelled.

4. threadLocksRef grows forever

Every thread that passes through getThreadSemaphore retains its semaphore for the adapter's lifetime — stopSession/stopAll/failed startups never remove it. Fix: delete the per-thread lock on session teardown and startup-failure paths (careful: the startSession replacement path must keep the lock it holds).

All four were verified against Cursor's code by reading; fixes 1-3 exist and are tested on the omp side (#9038, tests in OmpAdapter.test.ts) so the port is mechanical.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/server/src/provider/Layers/CursorAdapter.ts, focusing on sendTurn, interruptTurn, getThreadSemaphore, and session teardown or startup-failure paths. Read the corresponding fixes and tests in OmpAdapter.test.ts, then verify the Cursor adapter covers turn binding races, failed-turn terminal events, preparation-time cancellation, and per-thread lock cleanup.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.