@@ -738,6 +738,7 @@ function inspectVllmContainerOwnership(containerName: string): VllmContainerOwne
738738function vllmContainerReplacementTarget (
739739 containerName : string ,
740740 dockerEnv ?: Record < string , string > ,
741+ expectedContainerId ?: string ,
741742) : { ok : true ; containerId ?: string } | { ok : false ; reason : string } {
742743 const ownership = dockerEnv
743744 ? inspectVllmContainerOwnershipInDockerEnv ( containerName , dockerEnv )
@@ -762,6 +763,15 @@ function vllmContainerReplacementTarget(
762763 `Refusing single-host replacement because it would orphan the peer worker. Restore ${ NEMOCLAW_DGX_STATION_PEER_ENV } and select Nemotron Ultra to manage the pair.` ,
763764 } ;
764765 }
766+ if (
767+ expectedContainerId &&
768+ ( ownership . kind !== "managed" || ownership . containerId !== expectedContainerId )
769+ ) {
770+ return {
771+ ok : false ,
772+ reason : `Managed vLLM container "${ containerName } " changed after recovery. NemoClaw will not remove it. Retry onboarding.` ,
773+ } ;
774+ }
765775 return ownership . kind === "managed"
766776 ? { ok : true , containerId : ownership . containerId }
767777 : { ok : true } ;
@@ -868,6 +878,7 @@ function startContainer(
868878 dockerEnv : Record < string , string > = buildVllmDockerEnv ( ) ,
869879 resolveBridgeHost : ( dockerEnv : Record < string , string > ) => string = ( env ) =>
870880 resolveManagedVllmBridgeHost ( dockerCapture , env ) ,
881+ expectedReplacementContainerId ?: string ,
871882) : { ok : true ; containerId : string } | { ok : false ; reason : string } {
872883 emit ( `Starting vLLM container (${ profile . containerName } )` ) ;
873884 // The explicit download completed before this long-lived container starts,
@@ -892,6 +903,7 @@ function startContainer(
892903 const replacement = vllmContainerReplacementTarget (
893904 profile . containerName ,
894905 model . managedBearerAuth ? dockerEnv : undefined ,
906+ expectedReplacementContainerId ,
895907 ) ;
896908 if ( ! replacement . ok ) return replacement ;
897909 if ( replacement . containerId ) {
@@ -1735,6 +1747,7 @@ async function runVllmInstall(
17351747 // the guard first keeps a refused install free of both side effects.
17361748 // Port 25000 is not checked here: it belongs to the managed-cluster
17371749 // rendezvous contract and this single-node path never binds it.
1750+ let recoveredHostLocalContainerId : string | undefined ;
17381751 const servingPort = await opts . checkServingPort ?.( VLLM_PORT ) ;
17391752 if ( servingPort && ! servingPort . ok ) {
17401753 // An interrupted host-local install can leave its authenticated managed
@@ -1743,7 +1756,9 @@ async function runVllmInstall(
17431756 // credential fingerprint. The replacement guard below then removes the
17441757 // inspected container ID immediately before the new launch.
17451758 try {
1746- if ( recoverHostLocalManagedVllmEndpoint ( ) ) {
1759+ const recovered = recoverHostLocalManagedVllmEndpoint ( ) ;
1760+ if ( recovered ) {
1761+ recoveredHostLocalContainerId = recovered . containerId ;
17471762 // Continue through the ordinary managed-container replacement path.
17481763 } else {
17491764 printServingPortConflict ( servingPort ) ;
@@ -1830,6 +1845,7 @@ async function runVllmInstall(
18301845 const replacement = vllmContainerReplacementTarget (
18311846 runtimeProfile . containerName ,
18321847 model . managedBearerAuth ? localDockerEnv : undefined ,
1848+ recoveredHostLocalContainerId ,
18331849 ) ;
18341850 if ( ! replacement . ok ) {
18351851 console . error ( ` vLLM install failed: ${ replacement . reason } ` ) ;
@@ -2074,6 +2090,7 @@ async function runVllmInstall(
20742090 model ,
20752091 localDockerEnv ,
20762092 opts . resolveManagedBridgeHost ,
2093+ recoveredHostLocalContainerId ,
20772094 ) ;
20782095 if ( ! start . ok ) {
20792096 console . error ( ` vLLM install failed: ${ String ( start . reason ) } ` ) ;
0 commit comments