@@ -42,18 +42,30 @@ const canonicalDockerBindSource = (source: string) => {
4242 const desktopHostPath = source . startsWith ( '/host_mnt/' ) ? source . slice ( '/host_mnt' . length ) : source ;
4343 try { return realpathSync ( desktopHostPath ) ; } catch { return source ; }
4444} ;
45- const removeContainerOrThrow = ( profile : ContainerProfile ) => {
46- const remaining = createDeadline ( 30_000 ) ;
47- const before = spawnSync ( 'docker' , [ 'container' , 'inspect' , profile . name ] , {
48- encoding : 'utf8' , timeout : remaining ( ) , env : dockerEnvironment ( ) , stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
49- } ) ;
50- if ( before . status !== 0 ) {
45+ /** How long a killed `docker create` may still materialize its container in the daemon. */
46+ const CREATE_SETTLE_MS = 10_000 ;
47+ const sleep = ( ms : number ) => Atomics . wait ( new Int32Array ( new SharedArrayBuffer ( 4 ) ) , 0 , 0 , ms ) ;
48+ const removeContainerOrThrow = ( profile : ContainerProfile , createUnsettled = false ) => {
49+ const remaining = createDeadline ( 30_000 + ( createUnsettled ? CREATE_SETTLE_MS : 0 ) ) ;
50+ const settleBy = performance . now ( ) + ( createUnsettled ? CREATE_SETTLE_MS : 0 ) ;
51+ let before : ReturnType < typeof spawnSync > ;
52+ for ( ; ; ) {
53+ before = spawnSync ( 'docker' , [ 'container' , 'inspect' , profile . name ] , {
54+ encoding : 'utf8' , timeout : remaining ( ) , env : dockerEnvironment ( ) , stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
55+ } ) ;
56+ if ( before . status === 0 ) break ;
5157 const missing = ! before . error && / N o s u c h (?: o b j e c t | c o n t a i n e r ) / i. test ( `${ before . stdout ?? '' } \n${ before . stderr ?? '' } ` ) ;
5258 if ( ! missing ) throw new Error ( 'Failed to establish ownership of the agent container; staged credentials were retained.' ) ;
53- disposeContainerProfile ( profile ) ;
54- return ;
59+ if ( ! createUnsettled ) {
60+ disposeContainerProfile ( profile ) ;
61+ return ;
62+ }
63+ // A killed create may still land in the daemon; absence is not proof until the settle window passes.
64+ if ( performance . now ( ) >= settleBy )
65+ throw new Error ( 'Agent container creation did not settle; staged credentials were retained.' ) ;
66+ sleep ( 250 ) ;
5567 }
56- const inspected = JSON . parse ( before . stdout || '[]' ) [ 0 ] as { Config ?: { Labels ?: Record < string , string > } } | undefined ;
68+ const inspected = JSON . parse ( String ( before . stdout || '[]' ) ) [ 0 ] as { Config ?: { Labels ?: Record < string , string > } } | undefined ;
5769 if ( inspected ?. Config ?. Labels ?. [ 'io.codeboost.invocation' ] !== profile . ownershipId )
5870 throw new Error ( 'Agent container name is held by another invocation; staged credentials were retained.' ) ;
5971 const result = spawnSync ( 'docker' , [ 'rm' , '--force' , profile . name ] , {
@@ -230,16 +242,25 @@ export function validateContainer(container: string, profile: ContainerProfile,
230242export function createValidatedContainer ( profile : ContainerProfile , timeoutMs = 30_000 ,
231243 secrets : Readonly < Record < string , string > > = { } ) : string {
232244 const remaining = createDeadline ( timeoutMs ) ;
245+ let createUnsettled = false ;
233246 try {
234247 validateSecrets ( profile , secrets ) ;
235248 assertContainerProfile ( profile ) ;
236- docker ( profile . args , { timeoutMs : remaining ( ) , secrets } ) ;
249+ const createTimeout = remaining ( ) ;
250+ createUnsettled = true ;
251+ try { docker ( profile . args , { timeoutMs : createTimeout , secrets } ) ; }
252+ catch ( error ) {
253+ // A nonzero exit means the daemon answered; a killed client leaves the request in flight.
254+ createUnsettled = typeof ( error as { status ?: unknown } ) . status !== 'number' ;
255+ throw error ;
256+ }
257+ createUnsettled = false ;
237258 validateContainer ( profile . name , profile , remaining ( ) ) ;
238259 assertContainerProfile ( profile ) ;
239260 remaining ( ) ;
240261 return profile . name ;
241262 } catch ( error ) {
242- try { removeContainerOrThrow ( profile ) ; }
263+ try { removeContainerOrThrow ( profile , createUnsettled ) ; }
243264 catch ( cleanupError ) { throw new AggregateError ( [ error , cleanupError ] , 'Container creation failed and cleanup did not settle.' ) ; }
244265 throw error ;
245266 }
0 commit comments