@@ -44,6 +44,68 @@ import { PTYService } from "@/node/services/ptyService";
4444import type { TerminalWindowManager } from "@/desktop/terminalWindowManager" ;
4545import type { TerminalCreateParams , TerminalResizeParams } from "@/common/types/terminal" ;
4646import { ExtensionMetadataService } from "@/node/services/ExtensionMetadataService" ;
47+
48+ /** Maximum number of retry attempts when workspace name collides */
49+ const MAX_WORKSPACE_NAME_COLLISION_RETRIES = 3 ;
50+
51+ /**
52+ * Checks if an error indicates a workspace name collision
53+ */
54+ function isWorkspaceNameCollision ( error : string | undefined ) : boolean {
55+ return error ?. includes ( "Workspace already exists" ) ?? false ;
56+ }
57+
58+ /**
59+ * Generates a unique workspace name by appending a random suffix
60+ */
61+ function appendCollisionSuffix ( baseName : string ) : string {
62+ const suffix = Math . random ( ) . toString ( 36 ) . substring ( 2 , 6 ) ;
63+ return `${ baseName } -${ suffix } ` ;
64+ }
65+
66+ import type {
67+ Runtime ,
68+ WorkspaceCreationResult ,
69+ WorkspaceCreationParams ,
70+ } from "@/node/runtime/Runtime" ;
71+
72+ /**
73+ * Try to create a workspace, retrying with hash suffix on name collision.
74+ * Returns the final branch name used and the creation result.
75+ */
76+ async function createWorkspaceWithCollisionRetry (
77+ runtime : Runtime ,
78+ params : Omit < WorkspaceCreationParams , "directoryName" > ,
79+ baseBranchName : string
80+ ) : Promise < { branchName : string ; result : WorkspaceCreationResult } > {
81+ let currentBranchName = baseBranchName ;
82+
83+ for ( let attempt = 0 ; attempt <= MAX_WORKSPACE_NAME_COLLISION_RETRIES ; attempt ++ ) {
84+ const result = await runtime . createWorkspace ( {
85+ ...params ,
86+ branchName : currentBranchName ,
87+ directoryName : currentBranchName ,
88+ } ) ;
89+
90+ if ( result . success ) {
91+ return { branchName : currentBranchName , result } ;
92+ }
93+
94+ // If collision and not last attempt, retry with suffix
95+ if ( isWorkspaceNameCollision ( result . error ) && attempt < MAX_WORKSPACE_NAME_COLLISION_RETRIES ) {
96+ log . debug ( `Workspace name collision for "${ currentBranchName } ", retrying with suffix` ) ;
97+ currentBranchName = appendCollisionSuffix ( baseBranchName ) ;
98+ continue ;
99+ }
100+
101+ // Non-collision error or exhausted retries - return failure
102+ return { branchName : currentBranchName , result } ;
103+ }
104+
105+ // Should never reach here due to return in final iteration
106+ throw new Error ( "Unexpected: workspace creation loop completed without return" ) ;
107+ }
108+
47109import { generateWorkspaceName } from "./workspaceTitleGenerator" ;
48110/**
49111 * IpcMain - Manages all IPC handlers and service coordination
@@ -307,18 +369,21 @@ export class IpcMain {
307369
308370 const initLogger = this . createInitLogger ( workspaceId ) ;
309371
310- const createResult = await runtime . createWorkspace ( {
311- projectPath ,
312- branchName ,
313- trunkBranch : recommendedTrunk ,
314- directoryName : branchName ,
315- initLogger ,
316- } ) ;
372+ // Create workspace with automatic collision retry
373+ const { branchName : finalBranchName , result : createResult } =
374+ await createWorkspaceWithCollisionRetry (
375+ runtime ,
376+ { projectPath , branchName, trunkBranch : recommendedTrunk , initLogger } ,
377+ branchName
378+ ) ;
317379
318380 if ( ! createResult . success || ! createResult . workspacePath ) {
319381 return Err ( { type : "unknown" , raw : createResult . error ?? "Failed to create workspace" } ) ;
320382 }
321383
384+ // Use the final branch name (may have suffix if collision occurred)
385+ branchName = finalBranchName ;
386+
322387 const projectName =
323388 projectPath . split ( "/" ) . pop ( ) ?? projectPath . split ( "\\" ) . pop ( ) ?? "unknown" ;
324389
@@ -618,19 +683,21 @@ export class IpcMain {
618683
619684 const initLogger = this . createInitLogger ( workspaceId ) ;
620685
621- // Phase 1: Create workspace structure (FAST - returns immediately)
622- const createResult = await runtime . createWorkspace ( {
623- projectPath,
624- branchName,
625- trunkBranch : normalizedTrunkBranch ,
626- directoryName : branchName , // Use branch name as directory name
627- initLogger,
628- } ) ;
686+ // Phase 1: Create workspace structure with retry on name collision
687+ const { branchName : finalBranchName , result : createResult } =
688+ await createWorkspaceWithCollisionRetry (
689+ runtime ,
690+ { projectPath, branchName, trunkBranch : normalizedTrunkBranch , initLogger } ,
691+ branchName
692+ ) ;
629693
630694 if ( ! createResult . success || ! createResult . workspacePath ) {
631695 return { success : false , error : createResult . error ?? "Failed to create workspace" } ;
632696 }
633697
698+ // Use the final branch name (may have suffix if collision occurred)
699+ branchName = finalBranchName ;
700+
634701 const projectName =
635702 projectPath . split ( "/" ) . pop ( ) ?? projectPath . split ( "\\" ) . pop ( ) ?? "unknown" ;
636703
0 commit comments