Skip to content

Preserve provider bindings when stopping sessions#2125

Merged
juliusmarminge merged 3 commits intopingdotgg:mainfrom
HaukeSchnau:codex/preserve-codex-binding-on-reap
Apr 17, 2026
Merged

Preserve provider bindings when stopping sessions#2125
juliusmarminge merged 3 commits intopingdotgg:mainfrom
HaukeSchnau:codex/preserve-codex-binding-on-reap

Conversation

@HaukeSchnau
Copy link
Copy Markdown
Contributor

@HaukeSchnau HaukeSchnau commented Apr 17, 2026

Closes #2121

Summary

  • preserve provider bindings when the idle session reaper stops a session
  • keep explicit stop-session behavior destructive so manual stop still detaches the provider thread
  • add coverage for preserved-binding recovery and the reaper stop payload

Why

Idle reaping currently uses the same destructive stop path as an explicit manual stop. For the Codex provider, that drops the resume cursor and forces the next turn onto a fresh Codex thread instead of resuming the previous provider thread.

This change keeps the resume binding for idle cleanup only, so a later send can recover the same provider thread while leaving explicit stop semantics unchanged.

Verification

  • bun fmt
  • bun x vitest run apps/server/src/provider/Layers/ProviderService.test.ts apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
  • bun lint
  • bun typecheck
  • manual end-to-end validation against a real T3 + Codex flow, verifying that a thread still resumes after the idle reaper stops the provider session

Notes

  • callback state for old approvals or user-input requests is still not resumable; this only preserves the provider resume handle across idle cleanup
  • bun lint passes with existing repo warnings outside this patch

Note

Medium Risk
Changes provider session lifecycle/persistence semantics (stop no longer unbinds), which can affect routing and recovery paths across providers and restarts; coverage is improved but behavior changes are system-wide.

Overview
Provider session stopping is changed from a destructive unbind to a soft stop: ProviderServiceLive.stopSession now upserts the thread binding with status: "stopped" (and clears activeTurnId) instead of deleting it, keeping the persisted provider + resume cursor so a later sendTurn can recover/resume the same provider thread.

ProviderSessionDirectory removes the remove(threadId) API entirely, and tests are updated/added to verify stopped bindings remain persisted and that routing can resume after a stop. ProviderCommandReactor session restart logic is tightened to always prefer the thread-bound provider and to only drop resumeCursor on model-change restarts, with test reordering/adjustments around restart-failure and provider-switch rejection.

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

Note

Preserve provider bindings when stopping sessions instead of deleting them

  • ProviderSessionDirectory removes the remove method; stopped sessions are now persisted with status: 'stopped' and activeTurnId: null rather than deleted from the directory.
  • ProviderService.stopSession upserts the binding on stop, so a subsequent sendTurn can resume from the correct resumeCursor and provider without re-binding from scratch.
  • ProviderCommandReactor no longer restarts a session solely due to a provider change; it always uses the thread's bound provider and preserves resumeCursor unless a runtime mode or model-change condition requires a restart.
  • Behavioral Change: Sessions that were previously cleaned up on stop are now retained in persistence; any code path that relied on a missing binding after stop will behave differently.

Macroscope summarized b33fd1f.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

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

Run ID: 94b6f852-fad1-407c-bfd7-4748337e9e61

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@github-actions github-actions bot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 17, 2026
Copy link
Copy Markdown

@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: 560da15384

ℹ️ 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".

const reaped = yield* providerService
.stopSession({
threadId: binding.threadId,
preserveBinding: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear preserved binding when explicit stop is requested

Calling providerService.stopSession with preserveBinding: true during idle reaping leaves a persisted resume binding even after the runtime session is closed, but processSessionStopRequested only performs a destructive provider stop when thread.session.status !== "stopped" (apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:714-716). In the common reaped state (session already marked stopped), an explicit thread.session.stop command no longer removes that binding, so a later turn can still resume via the persisted cursor (ProviderService.startSession reuses persisted binding state). This makes explicit stop non-destructive for already-reaped threads and breaks the stated manual-stop semantics for that scenario.

Useful? React with 👍 / 👎.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 17, 2026

Approvability

Verdict: Needs human review

This PR changes session lifecycle behavior by removing the ability to delete provider bindings on stop. Two unresolved review comments identify that the implementation contradicts the stated intent - manual stops now preserve bindings when they should be destructive, allowing unintended session resumption.

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

@juliusmarminge
Copy link
Copy Markdown
Member

reading this now i don't remember why we ever remove the provider binding 🤔

- Keep persisted provider bindings on stop/reap
- Remove obsolete `preserveBinding` stop flag

Co-authored-by: codex <codex@users.noreply.github.com>
@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 Apr 17, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

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 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 938a082. Configure here.

runtimePayload: {
activeTurnId: null,
},
});
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.

Manual stop preserves binding, contradicting stated destructive intent

High Severity

stopSession now always upserts the binding with status: "stopped", but the PR intent is to only preserve bindings for idle-reaper stops while keeping manual/explicit stops destructive. The preserveBinding conditional mentioned in the PR description was never implemented — ProviderStopSessionInput still only has threadId, and the remove method was completely deleted from ProviderSessionDirectory. After a manual stop, the next sendTurn will recover the same provider thread via the persisted resumeCursor instead of starting fresh.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 938a082. Configure here.

Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts Outdated
@juliusmarminge juliusmarminge changed the title Preserve provider bindings when idle-reaping sessions Preserve provider bindings when stopping sessions Apr 17, 2026
- Keep an existing thread bound to its provider after reap
- Add regression coverage for first-turn and post-bind provider changes
@juliusmarminge juliusmarminge merged commit 721b6b4 into pingdotgg:main Apr 17, 2026
12 checks passed
juliusmarminge added a commit to nexxeln/t3code that referenced this pull request Apr 17, 2026
The `remove` method was removed from `ProviderSessionDirectoryShape` in
pingdotgg#2125 (Preserve provider bindings when stopping sessions). The test mock
still referenced it, causing a typecheck failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
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.

[Bug]: Claude losing earlier messages / not compacting correctly

2 participants