Skip to content

Commit 6dc319e

Browse files
fix: release write fence when workspace init settles on error
onUserResolved's readiness watcher only unblocked writes when teamWorkspaceStore.initState reached 'ready'. initState can also settle permanently at 'error' (no workspaces available, retries exhausted), which the watcher never accounted for -- the storage fence then stayed on for the rest of the session with no recovery path. Found by CodeRabbit on #14337.
1 parent 01facd4 commit 6dc319e

2 files changed

Lines changed: 32 additions & 6 deletions

File tree

src/platform/workflow/persistence/composables/useWorkflowPersistenceV2.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ vi.mock('@/platform/distribution/types', () => ({
143143
}))
144144

145145
const teamWorkspaceStoreMocks = reactive({
146-
initState: 'uninitialized' as 'uninitialized' | 'ready',
146+
initState: 'uninitialized' as 'uninitialized' | 'ready' | 'error',
147147
activeWorkspaceId: null as string | null
148148
})
149149

@@ -881,6 +881,27 @@ describe('useWorkflowPersistenceV2', () => {
881881
expect(completeTransitionSpy).toHaveBeenCalledOnce()
882882
})
883883

884+
it('releases the write fence when workspace initialization fails permanently', async () => {
885+
distributionMocks.isCloud = true
886+
const completeTransitionSpy = vi.spyOn(
887+
storageIO,
888+
'completeWorkflowLogoutTransition'
889+
)
890+
mountWorkflowPersistence()
891+
892+
const onLogout = currentUserMocks.onUserLogout.mock.calls[0][0]
893+
const onUserResolved = currentUserMocks.onUserResolved.mock.calls[0][0]
894+
onLogout()
895+
onUserResolved({ id: 'user-a' })
896+
897+
expect(completeTransitionSpy).not.toHaveBeenCalled()
898+
899+
teamWorkspaceStoreMocks.initState = 'error'
900+
await nextTick()
901+
902+
expect(completeTransitionSpy).toHaveBeenCalledOnce()
903+
})
904+
884905
it('waits for workspace readiness and drops pending pre-logout edits', async () => {
885906
distributionMocks.isCloud = true
886907
const sourceWorkspaceId = 'workspace-a'

src/platform/workflow/persistence/composables/useWorkflowPersistenceV2.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,21 @@ export function useWorkflowPersistenceV2() {
158158
if (!isCloud) return
159159
stopPendingWorkspaceReadinessWatcher()
160160

161-
const isWorkspaceReady = () =>
162-
teamWorkspaceStore.initState === 'ready' &&
163-
teamWorkspaceStore.activeWorkspaceId !== null
164-
if (isWorkspaceReady()) {
161+
// Release the fence once initialization concludes either way: a resolved
162+
// workspace, or a permanent init failure. Waiting on 'ready' alone would
163+
// leave writes blocked for the rest of the session if init settles on
164+
// 'error' (e.g. no workspaces available, retries exhausted).
165+
const isWorkspaceInitConcluded = () =>
166+
(teamWorkspaceStore.initState === 'ready' &&
167+
teamWorkspaceStore.activeWorkspaceId !== null) ||
168+
teamWorkspaceStore.initState === 'error'
169+
if (isWorkspaceInitConcluded()) {
165170
completeWorkflowLogoutTransition()
166171
return
167172
}
168173

169174
stopWorkspaceReadinessWatcher = whenever(
170-
isWorkspaceReady,
175+
isWorkspaceInitConcluded,
171176
() => {
172177
stopWorkspaceReadinessWatcher = undefined
173178
completeWorkflowLogoutTransition()

0 commit comments

Comments
 (0)