@@ -1117,6 +1117,7 @@ function orderGroupsByWorkspaceProjectOrder(
11171117
11181118function collectDuplicateProjectLeafNames ( groups : UiProjectGroup [ ] , rootsState : WorkspaceRootsState | null ) : Set < string > {
11191119 const rootByLeafName = new Map < string , Set < string > > ( )
1120+ const canonicalWorkspaceRootCountsByLeafName = new Map < string , number > ( )
11201121 const addPath = ( value : string ) : void => {
11211122 const normalizedPath = normalizePathForUi ( value ) . trim ( )
11221123 if ( ! normalizedPath ) return
@@ -1126,9 +1127,23 @@ function collectDuplicateProjectLeafNames(groups: UiProjectGroup[], rootsState:
11261127 rootByLeafName . set ( leafName , existing )
11271128 }
11281129
1129- for ( const rootPath of rootsState ?. order ?? [ ] ) addPath ( rootPath )
1130+ for ( const rootPath of rootsState ?. order ?? [ ] ) {
1131+ const normalizedRootPath = normalizePathForUi ( rootPath ) . trim ( )
1132+ if ( ! normalizedRootPath ) continue
1133+ const leafName = toProjectName ( normalizedRootPath )
1134+ if ( ! isManagedCodexWorktreePath ( normalizedRootPath ) ) {
1135+ canonicalWorkspaceRootCountsByLeafName . set ( leafName , ( canonicalWorkspaceRootCountsByLeafName . get ( leafName ) ?? 0 ) + 1 )
1136+ }
1137+ addPath ( rootPath )
1138+ }
11301139 for ( const group of groups ) {
1131- for ( const thread of group . threads ) addPath ( thread . cwd )
1140+ for ( const thread of group . threads ) {
1141+ const normalizedCwd = normalizePathForUi ( thread . cwd ) . trim ( )
1142+ const leafName = toProjectName ( normalizedCwd )
1143+ const isRegisteredRoot = rootsState ?. order . some ( ( rootPath ) => normalizePathForUi ( rootPath ) . trim ( ) === normalizedCwd ) === true
1144+ if ( isManagedCodexWorktreePath ( normalizedCwd ) && ! isRegisteredRoot && canonicalWorkspaceRootCountsByLeafName . get ( leafName ) === 1 ) continue
1145+ addPath ( thread . cwd )
1146+ }
11321147 }
11331148
11341149 const duplicateLeafNames = new Set < string > ( )
@@ -1138,20 +1153,53 @@ function collectDuplicateProjectLeafNames(groups: UiProjectGroup[], rootsState:
11381153 return duplicateLeafNames
11391154}
11401155
1156+ function isManagedCodexWorktreePath ( value : string ) : boolean {
1157+ return value . includes ( '/.codex/worktrees/' )
1158+ }
1159+
11411160function disambiguateProjectGroupsByCwd (
11421161 groups : UiProjectGroup [ ] ,
11431162 rootsState : WorkspaceRootsState | null ,
11441163) : UiProjectGroup [ ] {
11451164 const duplicateLeafNames = collectDuplicateProjectLeafNames ( groups , rootsState )
11461165 if ( duplicateLeafNames . size === 0 ) return groups
11471166
1167+ const uniqueCanonicalWorkspaceRootLeafNames = new Set < string > ( )
1168+ const duplicateCanonicalWorkspaceRootLeafNames = new Set < string > ( )
1169+ const canonicalWorkspaceRootByLeafName = new Map < string , string > ( )
1170+ const registeredWorkspaceRoots = new Set < string > ( )
1171+ for ( const rootPath of rootsState ?. order ?? [ ] ) {
1172+ const normalizedRootPath = normalizePathForUi ( rootPath ) . trim ( )
1173+ if ( ! normalizedRootPath ) continue
1174+ registeredWorkspaceRoots . add ( normalizedRootPath )
1175+ if ( isManagedCodexWorktreePath ( normalizedRootPath ) ) continue
1176+ const leafName = toProjectName ( normalizedRootPath )
1177+ if ( uniqueCanonicalWorkspaceRootLeafNames . has ( leafName ) ) {
1178+ uniqueCanonicalWorkspaceRootLeafNames . delete ( leafName )
1179+ duplicateCanonicalWorkspaceRootLeafNames . add ( leafName )
1180+ canonicalWorkspaceRootByLeafName . delete ( leafName )
1181+ } else if ( ! duplicateCanonicalWorkspaceRootLeafNames . has ( leafName ) ) {
1182+ uniqueCanonicalWorkspaceRootLeafNames . add ( leafName )
1183+ canonicalWorkspaceRootByLeafName . set ( leafName , normalizedRootPath )
1184+ }
1185+ }
1186+
11481187 const disambiguatedGroups : UiProjectGroup [ ] = [ ]
11491188 const groupsByProjectName = new Map < string , UiProjectGroup > ( )
11501189 for ( const group of groups ) {
11511190 for ( const thread of group . threads ) {
11521191 const normalizedCwd = normalizePathForUi ( thread . cwd ) . trim ( )
11531192 const leafName = toProjectName ( normalizedCwd )
1154- const projectName = normalizedCwd && duplicateLeafNames . has ( leafName ) ? normalizedCwd : group . projectName
1193+ const isRegisteredRoot = registeredWorkspaceRoots . has ( normalizedCwd )
1194+ const isCanonicalWorktreeThread = isManagedCodexWorktreePath ( normalizedCwd )
1195+ && ! isRegisteredRoot
1196+ && uniqueCanonicalWorkspaceRootLeafNames . has ( leafName )
1197+ let projectName = group . projectName
1198+ if ( isCanonicalWorktreeThread && duplicateLeafNames . has ( leafName ) ) {
1199+ projectName = canonicalWorkspaceRootByLeafName . get ( leafName ) ?? group . projectName
1200+ } else if ( normalizedCwd && duplicateLeafNames . has ( leafName ) ) {
1201+ projectName = normalizedCwd
1202+ }
11551203 const nextThread = thread . projectName === projectName ? thread : { ...thread , projectName }
11561204 const existingGroup = groupsByProjectName . get ( projectName )
11571205 if ( existingGroup ) {
0 commit comments