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
@@ -1,5 +1,6 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
@@ -9,7 +10,12 @@ import { useSessions } from '@/sync/sync-context';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useDeviceInfo } from '@/lib/device';
import { checkIsGitRepository } from '@/lib/gitApi';
import { getWorktreeSetupCommands, saveWorktreeSetupCommands } from '@/lib/openchamberConfig';
import {
getWorktreeSetupCommands,
getWorktreeSetupWaitEnabled,
saveWorktreeSetupCommands,
saveWorktreeSetupWaitEnabled,
} from '@/lib/openchamberConfig';
import { listProjectWorktrees } from '@/lib/worktrees/worktreeManager';
import { sessionEvents } from '@/lib/sessionEvents';
import type { WorktreeMetadata } from '@/types/worktree';
@@ -33,6 +39,7 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
const [setupCommands, setSetupCommands] = React.useState<string[]>([]);
const [waitForSetupCommands, setWaitForSetupCommands] = React.useState(false);
const [isLoadingCommands, setIsLoadingCommands] = React.useState(false);
const [isGitRepoLocal, setIsGitRepoLocal] = React.useState<boolean | null>(null);
const [availableWorktrees, setAvailableWorktrees] = React.useState<WorktreeMetadata[]>([]);
@@ -127,13 +134,18 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
(async () => {
try {
const commands = await getWorktreeSetupCommands(projectRef);
const [commands, waitForSetup] = await Promise.all([
getWorktreeSetupCommands(projectRef),
getWorktreeSetupWaitEnabled(projectRef),
]);
if (!cancelled) {
setSetupCommands(commands.length > 0 ? commands : ['']);
setWaitForSetupCommands(waitForSetup);
}
} catch {
if (!cancelled) {
setSetupCommands(['']);
setWaitForSetupCommands(false);
}
} finally {
if (!cancelled) {
@@ -179,6 +191,13 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
void persistSetupCommands(setupCommands);
}, [persistSetupCommands, setupCommands]);
const handleWaitForSetupCommandsChange = React.useCallback((enabled: boolean) => {
setWaitForSetupCommands(enabled);
if (projectRef) {
void saveWorktreeSetupWaitEnabled(projectRef, enabled);
}
}, [projectRef]);
// Delete worktree handler
const handleDeleteWorktree = React.useCallback((worktree: WorktreeMetadata) => {
const normalize = (value: string): string => value.replace(/\\/g, '/').replace(/\/+$/, '');
@@ -322,6 +341,22 @@ export const WorktreeSectionContent: React.FC<WorktreeSectionContentProps> = ({
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.openchamber.worktrees.setup.addCommand')}
</Button>
<label
data-settings-item="projects.worktree.setup.wait"
className="flex cursor-pointer items-center gap-2 py-1.5"
>
<Checkbox
checked={waitForSetupCommands}
onChange={handleWaitForSetupCommandsChange}
ariaLabel={t('settings.openchamber.worktrees.setup.waitForCommandsAria')}
/>
<span className={cn(
'typography-ui-label font-normal',
waitForSetupCommands ? 'text-foreground' : 'text-foreground/60'
)}>
{t('settings.openchamber.worktrees.setup.waitForCommands')}
</span>
</label>
</div>
)}
</div>
@@ -33,7 +33,8 @@ import * as sessionActions from '@/sync/session-actions';
import { useConfigStore } from '@/stores/useConfigStore';
import { validateWorktreeCreate, createWorktree } from '@/lib/worktrees/worktreeManager';
import { withWorktreeUpstreamDefaults } from '@/lib/worktrees/worktreeCreate';
import { getWorktreeSetupCommands } from '@/lib/openchamberConfig';
import { waitForWorktreeBootstrap } from '@/lib/worktrees/worktreeBootstrap';
import { getWorktreeSetupCommands, getWorktreeSetupWaitEnabled } from '@/lib/openchamberConfig';
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
import { generateBranchSlug } from '@/lib/git/branchNameGenerator';
import { renderMagicPrompt } from '@/lib/magicPrompts';
@@ -856,6 +857,10 @@ export function NewWorktreeDialog({
let createdSessionId: string | null = null;
if (shouldCreateSession) {
if (await getWorktreeSetupWaitEnabled(projectRef)) {
await waitForWorktreeBootstrap(metadata.path);
}
const sessionTitle = linkedIssue
? `#${linkedIssue.number} ${linkedIssue.title}`.trim()
: linkedPrState