Skip to content

fix(app-server): emit turn/started only when turn actually starts#13261

Merged
owenlin0 merged 2 commits intomainfrom
owen/mark_thread_as_active_when_turn_started
Mar 3, 2026
Merged

fix(app-server): emit turn/started only when turn actually starts#13261
owenlin0 merged 2 commits intomainfrom
owen/mark_thread_as_active_when_turn_started

Conversation

@owenlin0
Copy link
Copy Markdown
Collaborator

@owenlin0 owenlin0 commented Mar 2, 2026

This is a follow-up for #13047

Why

We had a race where turn/started could be observed before the thread had actually transitioned to Active. This was because we eagerly emitted turn/started in the request handler for turn/start (and review/start).

That was showing up as flaky thread/resume tests, but the real issue was broader: a client could see turn/started and still get back an idle thread immediately afterward.

The first idea was to eagerly call thread_watch_manager.note_turn_started(...) from the turn/start request path. That turns out to be unsafe, because submit(Op::UserInput) only queues work. If a turn starts and completes quickly, request-path bookkeeping can race with the real lifecycle events and leave stale running state behind.

The real fix is to move turn/started to emit only after the turn actually starts, so we do that by waiting for the EventMsg::TurnStarted notification emitted by codex core. We do this for both turn/start and review/start.

I also verified this change is safe for our first-party codex apps - they don't have any assumptions that turn/started is emitted before the RPC response to turn/start (which is correct anyway).

I also removed single_client_mode since it isn't really necessary now.

Testing

  • cargo test -p codex-app-server thread_resume -- --nocapture
  • cargo test -p codex-app-server 'suite::v2::turn_start::turn_start_emits_notifications_and_accepts_model_override' -- --exact --nocapture
  • cargo test -p codex-app-server
@owenlin0 owenlin0 marked this pull request as ready for review March 2, 2026 18:08
@owenlin0
Copy link
Copy Markdown
Collaborator Author

owenlin0 commented Mar 2, 2026

@codex review

@owenlin0 owenlin0 requested a review from bolinfest March 2, 2026 18:08
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fc30b6d1b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@owenlin0
Copy link
Copy Markdown
Collaborator Author

owenlin0 commented Mar 2, 2026

@codex review

@owenlin0 owenlin0 changed the title fix(app-server): mark thread as active before turn/started Mar 2, 2026
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60918ab4ac

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

thread_watch_manager
.note_turn_started(&conversation_id.to_string())
.await;
if let ApiVersion::V2 = api_version {
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.

P1 Badge Emit turn/started for V2 even when listener began as V1

turn/started is now sent only behind if let ApiVersion::V2 = api_version in apply_bespoke_event_handling. The listener’s api_version is fixed at startup and not refreshed when an existing listener is reused (ensure_listener_task_running_task returns early if listener_matches). If a V1 listener exists first, later V2 turn/start calls on that thread can receive no turn/started notification.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

don't care about this edge case

@owenlin0 owenlin0 requested a review from celia-oai March 2, 2026 21:29
@owenlin0 owenlin0 enabled auto-merge (squash) March 3, 2026 00:43
@owenlin0 owenlin0 disabled auto-merge March 3, 2026 00:43
@owenlin0 owenlin0 merged commit 146b798 into main Mar 3, 2026
68 of 75 checks passed
@owenlin0 owenlin0 deleted the owen/mark_thread_as_active_when_turn_started branch March 3, 2026 00:43
@github-actions github-actions bot locked and limited conversation to collaborators Mar 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

2 participants