@@ -35,9 +35,11 @@ const { readRepoSettings } = require('../lib/repo-settings');
3535const { provisionClaudeCredentials } = require ( './claude-credentials' ) ;
3636const {
3737 CopyContainmentError,
38+ copyErrorFromPayload,
3839 createCopyBoundary,
3940 resolveCopyPath,
4041 resolveSourcePath,
42+ validateRelativePath,
4143} = require ( './copy-containment' ) ;
4244
4345const DEFAULT_WORKTREE_SETUP_TIMEOUT_MS = 15 * 60 * 1000 ;
@@ -1323,12 +1325,9 @@ class IsolationManager {
13231325
13241326 function handleEntry ( entry , srcPath , relPath , relativePath , ancestorDirectories ) {
13251327 if ( entry . isSymbolicLink ( ) ) {
1326- const targetStats = fs . statSync ( srcPath ) ;
1328+ const resolvedSourcePath = resolveSourcePath ( copyBoundary , relPath ) ;
1329+ const targetStats = fs . statSync ( resolvedSourcePath ) ;
13271330 if ( targetStats . isDirectory ( ) ) {
1328- const resolvedSourcePath = resolveSourcePath ( copyBoundary , relPath ) ;
1329- if ( ancestorDirectories . has ( resolvedSourcePath ) ) {
1330- throw new CopyContainmentError ( relPath , 'source directory symlink creates a cycle' ) ;
1331- }
13321331 directories . add ( relPath ) ;
13331332 collectFiles ( relPath , ancestorDirectories ) ;
13341333 return ;
@@ -1365,8 +1364,9 @@ class IsolationManager {
13651364 continue ;
13661365 }
13671366
1368- const srcPath = path . join ( currentSrc , entry . name ) ;
1369- const relPath = relativePath ? path . join ( relativePath , entry . name ) : entry . name ;
1367+ const entryName = validateRelativePath ( entry . name ) ;
1368+ const srcPath = path . join ( currentSrc , entryName ) ;
1369+ const relPath = relativePath ? path . join ( relativePath , entryName ) : entryName ;
13701370
13711371 try {
13721372 handleEntry ( entry , srcPath , relPath , relativePath , childAncestors ) ;
@@ -1428,6 +1428,7 @@ class IsolationManager {
14281428 }
14291429
14301430 // Spawn workers and wait for completion
1431+ const workers = [ ] ;
14311432 const workerPromises = chunks . map ( ( chunk ) => {
14321433 return new Promise ( ( resolve , reject ) => {
14331434 const worker = new Worker ( workerPath , {
@@ -1438,9 +1439,11 @@ class IsolationManager {
14381439 expectedBoundary : copyBoundary ,
14391440 } ,
14401441 } ) ;
1442+ workers . push ( worker ) ;
1443+ let result = null ;
14411444
1442- worker . on ( 'message' , ( result ) => {
1443- resolve ( result ) ;
1445+ worker . on ( 'message' , ( workerResult ) => {
1446+ result = workerResult ;
14441447 } ) ;
14451448
14461449 worker . on ( 'error' , ( err ) => {
@@ -1450,6 +1453,12 @@ class IsolationManager {
14501453 worker . on ( 'exit' , ( code ) => {
14511454 if ( code !== 0 ) {
14521455 reject ( new Error ( `Worker exited with code ${ code } ` ) ) ;
1456+ } else if ( ! result ) {
1457+ reject ( new Error ( 'Copy worker exited without reporting a result' ) ) ;
1458+ } else if ( result . error ) {
1459+ reject ( copyErrorFromPayload ( result . error ) ) ;
1460+ } else {
1461+ resolve ( result ) ;
14531462 }
14541463 } ) ;
14551464 } ) ;
@@ -1458,15 +1467,11 @@ class IsolationManager {
14581467 // Wait for all workers to complete (proper async/await - no busy-wait!)
14591468 // FIX: Previous version used busy-wait which blocked the event loop,
14601469 // preventing worker thread messages from being processed (timeout bug)
1461- const workerResults = await Promise . all ( workerPromises ) ;
1462- const failedResult = workerResults . find ( ( result ) => result . error ) ;
1463- if ( failedResult ) {
1464- const error = new Error ( failedResult . error . message ) ;
1465- error . name = failedResult . error . name || 'Error' ;
1466- if ( failedResult . error . code ) {
1467- error . code = failedResult . error . code ;
1468- }
1469- throw error ;
1470+ try {
1471+ await Promise . all ( workerPromises ) ;
1472+ } catch ( err ) {
1473+ await Promise . all ( workers . map ( ( worker ) => worker . terminate ( ) . catch ( ( ) => undefined ) ) ) ;
1474+ throw err ;
14701475 }
14711476 }
14721477
0 commit comments