fix(worktree): gate sessions on bootstrap readiness (#1762)

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
bashrusakh
2026-06-26 12:16:42 +03:00
committed by GitHub
co-authored by Leonid Skorobogatyy Bohdan Triapitsyn
parent 03e6f789a4
commit 8c1a24089d
20 changed files with 252 additions and 14 deletions
@@ -6,7 +6,10 @@ const registeredDirectories: Array<{ sessionID: string; directory: string }> = [
const ensureChildCalls: Array<{ directory: string; bootstrap?: boolean }> = [];
const worktreeMetadataCalls: Array<{ sessionId: string; path: string }> = [];
const worktreeCreateCalls: Array<{ project: { id?: string; path: string }; args: Record<string, unknown>; options: unknown }> = [];
const worktreeBootstrapWaitCalls: string[] = [];
const operationOrder: string[] = [];
let isGitRepository = false;
let waitForWorktreeSetup = false;
const createWorktreeWithDefaultsMock = mock((project: { id?: string; path: string }, args: Record<string, unknown>, options: unknown) => {
worktreeCreateCalls.push({ project, args, options });
return Promise.resolve({
@@ -52,12 +55,15 @@ mock.module('@/lib/opencode/client', () => ({
currentDirectory = previous;
}
},
createSession: async (params?: { title?: string }): Promise<Session> => ({
id: 'ses_multirun',
title: params?.title ?? '',
directory: currentDirectory,
time: { created: 1, updated: 1 },
} as Session),
createSession: async (params?: { title?: string }): Promise<Session> => {
operationOrder.push(`createSession:${currentDirectory}`);
return {
id: 'ses_multirun',
title: params?.title ?? '',
directory: currentDirectory,
time: { created: 1, updated: 1 },
} as Session;
},
},
}));
@@ -70,11 +76,20 @@ mock.module('@/lib/worktrees/worktreeCreate', () => ({
resolveRootTrackingRemote: mock(() => Promise.resolve(null)),
}));
mock.module('@/lib/worktrees/worktreeBootstrap', () => ({
waitForWorktreeBootstrap: (directory: string) => {
worktreeBootstrapWaitCalls.push(directory);
operationOrder.push(`wait:${directory}`);
return Promise.resolve();
},
}));
mock.module('@/lib/worktrees/worktreeStatus', () => ({
getRootBranch: mock(() => Promise.resolve('main')),
}));
mock.module('@/lib/openchamberConfig', () => ({
getWorktreeSetupWaitEnabled: mock(() => Promise.resolve(waitForWorktreeSetup)),
saveWorktreeSetupCommands: mock(() => Promise.resolve()),
}));
@@ -139,7 +154,10 @@ describe('useMultiRunStore', () => {
ensureChildCalls.length = 0;
worktreeMetadataCalls.length = 0;
worktreeCreateCalls.length = 0;
worktreeBootstrapWaitCalls.length = 0;
operationOrder.length = 0;
isGitRepository = false;
waitForWorktreeSetup = false;
childState.session = [];
childState.sessionTotal = 0;
childState.limit = 5;
@@ -181,7 +199,30 @@ describe('useMultiRunStore', () => {
expect(worktreeCreateCalls[0]?.project).toEqual({ id: 'project-1', path: '/repo' });
expect(worktreeCreateCalls[0]?.args.returnAfterDirectoryCreated).toBe(true);
expect(worktreeCreateCalls[0]?.options).toEqual({ resolvedRootTrackingRemote: null });
expect(worktreeBootstrapWaitCalls).toEqual([]);
expect(operationOrder).toEqual(['createSession:/repo-worktrees/fix-thing']);
expect(registeredDirectories).toEqual([{ sessionID: 'ses_multirun', directory: '/repo-worktrees/fix-thing' }]);
expect(worktreeMetadataCalls).toEqual([{ sessionId: 'ses_multirun', path: '/repo-worktrees/fix-thing' }]);
});
test('waits for isolated worktree bootstrap when setup wait is enabled', async () => {
isGitRepository = true;
waitForWorktreeSetup = true;
const result = await useMultiRunStore.getState().createMultiRun({
name: 'Fix thing',
isolateRuns: true,
groups: [{
prompt: 'Fix it',
models: [{ providerID: 'anthropic', modelID: 'claude-sonnet-4-5' }],
}],
});
expect(result?.sessionIds).toEqual(['ses_multirun']);
expect(worktreeBootstrapWaitCalls).toEqual(['/repo-worktrees/fix-thing']);
expect(operationOrder).toEqual([
'wait:/repo-worktrees/fix-thing',
'createSession:/repo-worktrees/fix-thing',
]);
});
});
+6 -1
View File
@@ -4,9 +4,10 @@ import { routeMessage, useSessionUIStore } from '@/sync/session-ui-store';
import { devtools } from 'zustand/middleware';
import type { CreateMultiRunParams, CreateMultiRunResult } from '@/types/multirun';
import { opencodeClient } from '@/lib/opencode/client';
import { saveWorktreeSetupCommands } from '@/lib/openchamberConfig';
import { getWorktreeSetupWaitEnabled, saveWorktreeSetupCommands } from '@/lib/openchamberConfig';
import type { ProjectRef } from '@/lib/worktrees/worktreeManager';
import { createWorktreeWithDefaults, resolveRootTrackingRemote } from '@/lib/worktrees/worktreeCreate';
import { waitForWorktreeBootstrap } from '@/lib/worktrees/worktreeBootstrap';
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
import { checkIsGitRepository } from '@/lib/gitApi';
import { useDirectoryStore } from './useDirectoryStore';
@@ -244,6 +245,10 @@ export const useMultiRunStore = create<MultiRunStore>()(
kind: 'standard' as const,
};
if (await getWorktreeSetupWaitEnabled(project)) {
await waitForWorktreeBootstrap(worktreeMetadata.path);
}
const session = await opencodeClient.withDirectory(
worktreeMetadata.path,
() => opencodeClient.createSession({ title: sessionTitle }),