@@ -44,6 +44,25 @@ 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+
49+ /** Maximum number of retry attempts when workspace name collides */
50+ const MAX_WORKSPACE_NAME_COLLISION_RETRIES = 3 ;
51+
52+ /**
53+ * Checks if an error indicates a workspace name collision
54+ */
55+ function isWorkspaceNameCollision ( error : string | undefined ) : boolean {
56+ return error ?. includes ( "Workspace already exists" ) ?? false ;
57+ }
58+
59+ /**
60+ * Generates a unique workspace name by appending a random suffix
61+ */
62+ function appendCollisionSuffix ( baseName : string ) : string {
63+ const suffix = Math . random ( ) . toString ( 36 ) . substring ( 2 , 6 ) ;
64+ return `${ baseName } -${ suffix } ` ;
65+ }
4766import { generateWorkspaceName } from "./workspaceTitleGenerator" ;
4867/**
4968 * IpcMain - Manages all IPC handlers and service coordination
@@ -307,18 +326,40 @@ export class IpcMain {
307326
308327 const initLogger = this . createInitLogger ( workspaceId ) ;
309328
310- const createResult = await runtime . createWorkspace ( {
311- projectPath,
312- branchName,
313- trunkBranch : recommendedTrunk ,
314- directoryName : branchName ,
315- initLogger,
316- } ) ;
329+ // Retry workspace creation with hash suffix on name collision
330+ let currentBranchName = branchName ;
331+ let createResult ;
332+ for ( let attempt = 0 ; attempt <= MAX_WORKSPACE_NAME_COLLISION_RETRIES ; attempt ++ ) {
333+ createResult = await runtime . createWorkspace ( {
334+ projectPath,
335+ branchName : currentBranchName ,
336+ trunkBranch : recommendedTrunk ,
337+ directoryName : currentBranchName ,
338+ initLogger,
339+ } ) ;
340+
341+ if ( createResult . success ) {
342+ break ;
343+ }
344+
345+ // If collision and not last attempt, retry with suffix
346+ if ( isWorkspaceNameCollision ( createResult . error ) && attempt < MAX_WORKSPACE_NAME_COLLISION_RETRIES ) {
347+ log . debug ( `Workspace name collision for "${ currentBranchName } ", retrying with suffix` ) ;
348+ currentBranchName = appendCollisionSuffix ( branchName ) ;
349+ continue ;
350+ }
317351
318- if ( ! createResult . success || ! createResult . workspacePath ) {
352+ // Non-collision error or exhausted retries
319353 return Err ( { type : "unknown" , raw : createResult . error ?? "Failed to create workspace" } ) ;
320354 }
321355
356+ if ( ! createResult ! . success || ! createResult ! . workspacePath ) {
357+ return Err ( { type : "unknown" , raw : createResult ! . error ?? "Failed to create workspace" } ) ;
358+ }
359+
360+ // Use the final branch name (may have suffix if collision occurred)
361+ branchName = currentBranchName ;
362+
322363 const projectName =
323364 projectPath . split ( "/" ) . pop ( ) ?? projectPath . split ( "\\" ) . pop ( ) ?? "unknown" ;
324365
@@ -337,7 +378,7 @@ export class IpcMain {
337378 config . projects . set ( projectPath , projectConfig ) ;
338379 }
339380 projectConfig . workspaces . push ( {
340- path : createResult . workspacePath ! ,
381+ path : createResult ! . workspacePath ! ,
341382 id : workspaceId ,
342383 name : branchName ,
343384 createdAt : metadata . createdAt ,
@@ -359,7 +400,7 @@ export class IpcMain {
359400 projectPath,
360401 branchName,
361402 trunkBranch : recommendedTrunk ,
362- workspacePath : createResult . workspacePath ,
403+ workspacePath : createResult ! . workspacePath ! ,
363404 initLogger,
364405 } )
365406 . catch ( ( error : unknown ) => {
@@ -618,19 +659,40 @@ export class IpcMain {
618659
619660 const initLogger = this . createInitLogger ( workspaceId ) ;
620661
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- } ) ;
662+ // Phase 1: Create workspace structure with retry on name collision
663+ let currentBranchName = branchName ;
664+ let createResult ;
665+ for ( let attempt = 0 ; attempt <= MAX_WORKSPACE_NAME_COLLISION_RETRIES ; attempt ++ ) {
666+ createResult = await runtime . createWorkspace ( {
667+ projectPath,
668+ branchName : currentBranchName ,
669+ trunkBranch : normalizedTrunkBranch ,
670+ directoryName : currentBranchName ,
671+ initLogger,
672+ } ) ;
629673
630- if ( ! createResult . success || ! createResult . workspacePath ) {
674+ if ( createResult . success ) {
675+ break ;
676+ }
677+
678+ // If collision and not last attempt, retry with suffix
679+ if ( isWorkspaceNameCollision ( createResult . error ) && attempt < MAX_WORKSPACE_NAME_COLLISION_RETRIES ) {
680+ log . debug ( `Workspace name collision for "${ currentBranchName } ", retrying with suffix` ) ;
681+ currentBranchName = appendCollisionSuffix ( branchName ) ;
682+ continue ;
683+ }
684+
685+ // Non-collision error or exhausted retries
631686 return { success : false , error : createResult . error ?? "Failed to create workspace" } ;
632687 }
633688
689+ if ( ! createResult ! . success || ! createResult ! . workspacePath ) {
690+ return { success : false , error : createResult ! . error ?? "Failed to create workspace" } ;
691+ }
692+
693+ // Use the final branch name (may have suffix if collision occurred)
694+ branchName = currentBranchName ;
695+
634696 const projectName =
635697 projectPath . split ( "/" ) . pop ( ) ?? projectPath . split ( "\\" ) . pop ( ) ?? "unknown" ;
636698
@@ -656,7 +718,7 @@ export class IpcMain {
656718 }
657719 // Add workspace to project config with full metadata
658720 projectConfig . workspaces . push ( {
659- path : createResult . workspacePath ! ,
721+ path : createResult ! . workspacePath ! ,
660722 id : workspaceId ,
661723 name : branchName ,
662724 createdAt : metadata . createdAt ,
@@ -684,7 +746,7 @@ export class IpcMain {
684746 projectPath,
685747 branchName,
686748 trunkBranch : normalizedTrunkBranch ,
687- workspacePath : createResult . workspacePath ,
749+ workspacePath : createResult ! . workspacePath ! ,
688750 initLogger,
689751 } )
690752 . catch ( ( error : unknown ) => {
0 commit comments