Skip to content

fix(provider): list only committed sessions#4191

Draft
bdsqqq wants to merge 4 commits into
pingdotgg:mainfrom
bdsqqq:fix/provider-session-ownership-clean
Draft

fix(provider): list only committed sessions#4191
bdsqqq wants to merge 4 commits into
pingdotgg:mainfrom
bdsqqq:fix/provider-session-ownership-clean

Conversation

@bdsqqq

@bdsqqq bdsqqq commented Jul 20, 2026

Copy link
Copy Markdown

What Changed

reconcile adapter sessions against persisted ownership bindings, and return only active sessions owned by the committed provider instance.

listing retries transitional snapshots under the per-thread ownership lock, and fails loudly when bindings can't be read.

Why

adapters can briefly expose a replacement child before its ownership binding commits. treating adapter state alone as authoritative makes unbound, stale, or wrong-instance sessions look active.

so listing now follows the same ownership boundary as lifecycle transitions. reconciliation under the lock lets us distinguish an in-progress transition from corrupt state without exposing the uncommitted child.

Stack

  1. #4189 — revoke credentials by provider session
  2. #4190 — make session ownership transitions atomic
  3. #4191 — list only committed sessions (this PR)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Fix listSessions to return only sessions matching their committed binding

  • Overhauls ProviderService to serialize session operations per-thread using per-thread semaphores and a generation counter, making session start, stop, and recovery transactional.
  • Adds prepare/commit/rollback phases for MCP session lifecycle so credential issuance and revocation are atomic relative to binding persistence.
  • listSessions now reconciles active sessions against persisted bindings under a thread lock instead of throwing on mismatches, filtering out sessions that are not owned by the current binding.
  • startSession gains an activeSession: 'reuse' | 'replace' option; ProviderCommandReactor defaults new starts to 'reuse' and restarts to 'replace'.
  • Adds compensation logic so that if binding persistence fails after a session starts, the session is stopped and MCP credentials are revoked.
  • Risk: significant behavioral change to session concurrency and listing — stale or unowned sessions are now silently excluded rather than causing errors.
📊 Macroscope summarized fd0b53d. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 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

Run ID: c5999e72-45b6-490c-85ea-d3da08282aa7

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.

Comment on lines +257 to +266
const commitMcpSession = Effect.fn("ProviderService.commitMcpSession")(function* (
prepared: PreparedMcpSession,
) {
if (
prepared.previous &&
prepared.previous.providerSessionId !== prepared.current?.providerSessionId
) {
yield* McpSessionRegistry.revokeActiveMcpProviderSession(prepared.previous.providerSessionId);
}
});

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.

🟡 Medium Layers/ProviderService.ts:257

When issueUncommittedMcpCredential returns undefined, prepareMcpSession keeps the previous MCP config installed (current is undefined), but commitMcpSession then revokes prepared.previous.providerSessionId because prepared.current?.providerSessionId is also undefined. A successful session start or recovery therefore leaves McpProviderSession pointing at a credential that has already been revoked, breaking subsequent MCP access whenever credential issuance is unavailable. The guard in commitMcpSession should skip revocation when current is undefined, not just when the session ids differ.

  const commitMcpSession = Effect.fn("ProviderService.commitMcpSession")(function* (
    prepared: PreparedMcpSession,
  ) {
-    if (
-      prepared.previous &&
-      prepared.previous.providerSessionId !== prepared.current?.providerSessionId
-    ) {
+    if (prepared.current && prepared.previous &&
+      prepared.previous.providerSessionId !== prepared.current.providerSessionId
+    ) {
      yield* McpSessionRegistry.revokeActiveMcpProviderSession(prepared.previous.providerSessionId);
    }
  });
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ProviderService.ts around lines 257-266:

When `issueUncommittedMcpCredential` returns `undefined`, `prepareMcpSession` keeps the previous MCP config installed (`current` is `undefined`), but `commitMcpSession` then revokes `prepared.previous.providerSessionId` because `prepared.current?.providerSessionId` is also `undefined`. A successful session start or recovery therefore leaves `McpProviderSession` pointing at a credential that has already been revoked, breaking subsequent MCP access whenever credential issuance is unavailable. The guard in `commitMcpSession` should skip revocation when `current` is `undefined`, not just when the session ids differ.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

same as the thread on #4190, fixed here in 91f004ef8.

the adapter now sees no stale config when credential issuance is unavailable, and rollback still restores the previous config on failure.

"provider.kind": routed.adapter.provider,
"provider.thread_id": input.threadId,
});
yield* advanceThreadGeneration(input.threadId);

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.

🟡 Medium Layers/ProviderService.ts:1057

getThreadLock permanently inserts every threadId into threadLocks, and neither this map nor threadGenerations is ever pruned after stopSession, runStopAll, or failed session starts. A long-running server accumulates a semaphore and generation entry for every thread it has ever seen, causing unbounded memory growth. Consider removing both entries for a thread when its session is stopped or fails to bind.

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

`getThreadLock` permanently inserts every `threadId` into `threadLocks`, and neither this map nor `threadGenerations` is ever pruned after `stopSession`, `runStopAll`, or failed session starts. A long-running server accumulates a semaphore and generation entry for every thread it has ever seen, causing unbounded memory growth. Consider removing both entries for a thread when its session is stopped or fails to bind.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

same concern as the thread on #4190, replied there.

listSessions is another possible queued borrower, so deleting the lock here has the same split-lock risk. gonna keep bounded cleanup out of this stack.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

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

Labels

size:L 100-499 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