fix(sync): settle a guessed session directory once its owner is known

Selecting a session whose directory this client has not indexed yet routes it
through the active directory. That is a deliberate, documented guess: it keeps
routing usable while the owning store bootstraps, and it is excluded from both
the resolver and persistence.

Nothing settled the guess afterwards. `setSessionDirectory` performs exactly
that promotion, but only confirmed destinations call it — a completed move or a
worktree this client created. A session whose directory the client learned about
later, such as one in a worktree created outside this client, kept the guess
forever: every message fetch was addressed to the parent repository, which does
not own the session.

Captured for such a session before this change, with the session already
indexed and its owning store known:

  routedDirectory          .../worktree/feature
  currentSessionDirectory  /repo            <- guess, never settled
  opencodeClientDirectory  /repo
  conflict                 selected -> /repo

and after:

  routedDirectory          .../worktree/feature
  currentSessionDirectory  .../worktree/feature
  opencodeClientDirectory  .../worktree/feature
  conflict                 null

Directory bootstrap completion is the moment the authoritative directory first
becomes readable, so the promotion runs there. It only ever promotes a guess:
a confirmed selection and a selection that has since moved on are both left
alone, and tests cover both directions.

This removes a real routing split-brain. It does not by itself fix the reported
symptom of a session created mid-session never rendering; that remains open.
This commit is contained in:
Bohdan Triapitsyn
2026-08-04 00:42:34 +03:00
parent 4773db83c5
commit a44d291cb5
3 changed files with 119 additions and 0 deletions
+11
View File
@@ -34,6 +34,7 @@ import { countSyncPerformance } from "./performance-diagnostics"
import { runBackgroundNetworkTask } from "@/lib/background-network"
import { setActionRefs } from "./session-actions"
import { setSyncRefs, getAllSyncSessions } from "./sync-refs"
import { useSessionUIStore } from "./session-ui-store"
import { stripSessionDiffSnapshots } from "./sanitize"
import { applySessionEventToGlobalSessions } from "./session-event-router"
import { syncDebug } from "./debug"
@@ -1949,6 +1950,16 @@ export function SyncProvider(props: {
const result = await runBootstrap(0)
if (result === "failed") throw new Error(`Directory bootstrap failed for ${directory}`)
// Selecting a session whose directory this client had not indexed yet
// routes it through the active directory as a documented guess. This is
// the moment that guess can be settled: the owning store now holds the
// session, so the authoritative directory is finally readable. Without
// this the guess survives, every fetch is addressed to a directory that
// does not own the session, and the session never renders.
if (result === "complete") {
useSessionUIStore.getState().adoptAuthoritativeSessionDirectory()
}
},
onDispose: (directory) => {
messageLoader.invalidateDirectory(directory)