@@ -158,22 +158,24 @@ export function createContainerProfile(options: ProfileOptions): ContainerProfil
158158 assertTaskFilesystems ( filesystems , invocation . clone ) ;
159159 assertVendorNetwork ( options . network , invocation ) ;
160160 if ( claimedNetworks . has ( options . network ) ) throw new Error ( 'Vendor network already belongs to another container profile.' ) ;
161- assertPhasePolicy ( options . policy , invocation ) ;
162- const command = assertAgentCommand ( options . command , options . policy , invocation . vendor ) ;
163- const sourceInput = captureInput ( options . inputDirectory ) ;
164- if ( invocation . vendor === 'codex' && ( ! options . codexAuthFile || options . claudeToken ) )
165- throw new Error ( 'Codex requires only its auth file.' ) ;
166- if ( invocation . vendor === 'claude' && ( ! options . claudeToken || options . codexAuthFile ) )
167- throw new Error ( 'Claude requires only its OAuth token.' ) ;
168- if ( options . claudeToken ?. includes ( '\0' ) ) throw new Error ( 'Claude OAuth token is malformed.' ) ;
169- if ( ! / ^ c o d e b o o s t - w o r k - [ 0 - 9 a - f - ] + $ / . test ( filesystems . workVolume )
170- || ! / ^ c o d e b o o s t - m e t a d a t a - [ 0 - 9 a - f - ] + $ / . test ( filesystems . metadataVolume )
171- || ! / ^ c o d e b o o s t - k e e p e r - [ 0 - 9 a - f - ] + $ / . test ( filesystems . keeper ) ) throw new Error ( 'Task filesystem identity is invalid.' ) ;
172- // Read through one no-follow descriptor so the path cannot be swapped between check and open.
173- const sourceAuth = options . codexAuthFile ? readCapturedFile ( options . codexAuthFile , 'Codex auth' ) : undefined ;
161+ // Own the network from here on, so any later failure removes it rather than leaking it.
162+ claimedNetworks . add ( options . network ) ;
174163 const cleanupDirectories : string [ ] = [ ] ;
175164 let codexAuthFile : string | undefined , authIdentity : FileIdentity | undefined ;
176165 try {
166+ assertPhasePolicy ( options . policy , invocation ) ;
167+ const command = assertAgentCommand ( options . command , options . policy , invocation . vendor ) ;
168+ const sourceInput = captureInput ( options . inputDirectory ) ;
169+ if ( invocation . vendor === 'codex' && ( ! options . codexAuthFile || options . claudeToken ) )
170+ throw new Error ( 'Codex requires only its auth file.' ) ;
171+ if ( invocation . vendor === 'claude' && ( ! options . claudeToken || options . codexAuthFile ) )
172+ throw new Error ( 'Claude requires only its OAuth token.' ) ;
173+ if ( options . claudeToken ?. includes ( '\0' ) ) throw new Error ( 'Claude OAuth token is malformed.' ) ;
174+ if ( ! / ^ c o d e b o o s t - w o r k - [ 0 - 9 a - f - ] + $ / . test ( filesystems . workVolume )
175+ || ! / ^ c o d e b o o s t - m e t a d a t a - [ 0 - 9 a - f - ] + $ / . test ( filesystems . metadataVolume )
176+ || ! / ^ c o d e b o o s t - k e e p e r - [ 0 - 9 a - f - ] + $ / . test ( filesystems . keeper ) ) throw new Error ( 'Task filesystem identity is invalid.' ) ;
177+ // Read through one no-follow descriptor so the path cannot be swapped between check and open.
178+ const sourceAuth = options . codexAuthFile ? readCapturedFile ( options . codexAuthFile , 'Codex auth' ) : undefined ;
177179 const inputDirectory = mkdtempSync ( join ( tmpdir ( ) , 'codeboost-input-' ) ) ;
178180 cleanupDirectories . push ( inputDirectory ) ;
179181 writeFileSync ( join ( inputDirectory , 'schema.json' ) , sourceInput . content ,
@@ -224,11 +226,12 @@ export function createContainerProfile(options: ProfileOptions): ContainerProfil
224226 auth : authIdentity ,
225227 cleanupDirectories : Object . freeze ( [ ...cleanupDirectories ] ) , filesystems, clone : invocation . clone ,
226228 network : options . network , policy : options . policy , invocation } ) ) ;
227- claimedNetworks . add ( options . network ) ;
228229 return profile ;
229230 } catch ( error ) {
230- try { removeOwnedDirectories ( cleanupDirectories ) ; }
231- catch ( cleanupError ) { throw new AggregateError ( [ error , cleanupError ] , 'Profile creation and cleanup both failed.' ) ; }
231+ const failures : unknown [ ] = [ ] ;
232+ try { removeOwnedDirectories ( cleanupDirectories ) ; } catch ( cleanupError ) { failures . push ( cleanupError ) ; }
233+ try { removeVendorNetwork ( options . network ) ; } catch ( cleanupError ) { failures . push ( cleanupError ) ; }
234+ if ( failures . length ) throw new AggregateError ( [ error , ...failures ] , 'Profile creation and cleanup both failed.' ) ;
232235 throw error ;
233236 }
234237}
0 commit comments