@@ -63,6 +63,8 @@ const HERMES_TOOL_GATEWAY_CONTROL_CONTRACT_PATH = path.join(
6363) ;
6464const HERMES_TOOL_GATEWAY_RUNTIME_MISMATCH_RECOVERY =
6565 "Reauthorize every managed-tool Hermes sandbox, then retry." ;
66+ const HERMES_TOOL_GATEWAY_UNOWNED_LISTENER_RECOVERY =
67+ "Stop the process holding that port, then retry." ;
6668const HERMES_TOOL_GATEWAY_CONTROL_CLIENT_SOURCE = [
6769 'const http = require("node:http");' ,
6870 "const [socketPath, route, timeoutValue] = process.argv.slice(1);" ,
@@ -118,8 +120,6 @@ const HERMES_TOOL_GATEWAY_CONTROL_CLIENT_SOURCE = [
118120 "});" ,
119121] . join ( "\n" ) ;
120122
121- let brokerStartedThisRun = false ;
122-
123123function sleep ( ms ) {
124124 const lock = new Int32Array ( new SharedArrayBuffer ( 4 ) ) ;
125125 Atomics . wait ( lock , 0 , 0 , ms ) ;
@@ -555,7 +555,7 @@ function preflightHermesToolGatewayCloneBinding(sandboxName) {
555555 }
556556
557557 const pid = readPid ( ) ;
558- const currentBrokerOwned = isHermesToolGatewayBrokerProcess ( pid ) || brokerStartedThisRun ;
558+ const currentBrokerOwned = isHermesToolGatewayBrokerProcess ( pid ) ;
559559 const currentBrokerHealthy = isHermesToolGatewayBrokerHealthy ( ) ;
560560 if ( currentBrokerHealthy && ! currentBrokerOwned ) {
561561 throw new Error ( "Hermes managed-tool broker health endpoint is not owned by NemoClaw" ) ;
@@ -738,8 +738,23 @@ function ensureHermesToolGatewayBroker(options = {}) {
738738 const desiredHash = brokerRuntimeHash ( ) ;
739739 const hashMatches = readBrokerHash ( ) === desiredHash ;
740740 const pid = readPid ( ) ;
741- const currentBrokerOwned = isHermesToolGatewayBrokerProcess ( pid ) || brokerStartedThisRun ;
742- const currentBrokerHealthy = currentBrokerOwned && isHermesToolGatewayBrokerHealthy ( ) ;
741+ const currentBrokerOwned = isHermesToolGatewayBrokerProcess ( pid ) ;
742+ const brokerHealthy = isHermesToolGatewayBrokerHealthy ( ) ;
743+ const currentBrokerHealthy = currentBrokerOwned && brokerHealthy ;
744+ // `/health` is unauthenticated on a fixed port, so reachability proves
745+ // liveness and never identity. Ownership comes only from a recorded pid that
746+ // still resolves to a running broker, re-proved on every call: a broker can
747+ // exit and leave the port free for another process to bind. Refuse before any
748+ // path can adopt, restart around, or stage credentials against a listener
749+ // NemoClaw cannot prove it owns.
750+ if ( brokerHealthy && ! currentBrokerOwned ) {
751+ console . error (
752+ "Hermes managed-tool broker health endpoint is not owned by NemoClaw; " +
753+ `refusing to reuse the listener on port ${ HERMES_TOOL_GATEWAY_PORT } . ` +
754+ HERMES_TOOL_GATEWAY_UNOWNED_LISTENER_RECOVERY ,
755+ ) ;
756+ return false ;
757+ }
743758 if ( options . startWithoutCredential ) {
744759 if ( currentBrokerHealthy ) {
745760 return hashMatches && fs . existsSync ( HERMES_TOOL_GATEWAY_CONTROL_SOCKET_PATH ) ;
@@ -752,7 +767,6 @@ function ensureHermesToolGatewayBroker(options = {}) {
752767 isHermesToolGatewayBrokerHealthy ( ) &&
753768 fs . existsSync ( HERMES_TOOL_GATEWAY_CONTROL_SOCKET_PATH )
754769 ) {
755- brokerStartedThisRun = true ;
756770 return true ;
757771 }
758772 sleep ( 250 ) ;
@@ -779,7 +793,6 @@ function ensureHermesToolGatewayBroker(options = {}) {
779793 refreshToken ,
780794 options . sandboxName ?? null ,
781795 ) ;
782- if ( registered ) brokerStartedThisRun = true ;
783796 return registered ;
784797 }
785798 if ( refreshPlan === "start-or-restart" ) {
@@ -791,33 +804,16 @@ function ensureHermesToolGatewayBroker(options = {}) {
791804 isHermesToolGatewayBrokerHealthy ( ) &&
792805 registerHermesToolGatewayRuntimeCredential ( refreshToken , options . sandboxName ?? null )
793806 ) {
794- brokerStartedThisRun = true ;
795807 return true ;
796808 }
797809 sleep ( 250 ) ;
798810 }
799811 return false ;
800812 }
801813
802- if (
803- ! options . forceRestart &&
804- hashMatches &&
805- brokerStartedThisRun &&
806- isHermesToolGatewayBrokerHealthy ( )
807- ) {
808- return true ;
809- }
810- if (
811- ! options . forceRestart &&
812- hashMatches &&
813- isHermesToolGatewayBrokerProcess ( pid ) &&
814- isHermesToolGatewayBrokerHealthy ( )
815- ) {
816- brokerStartedThisRun = true ;
817- return true ;
818- }
819- if ( ! options . forceRestart && hashMatches && isHermesToolGatewayBrokerHealthy ( ) ) {
820- brokerStartedThisRun = true ;
814+ // `currentBrokerHealthy` already requires ownership, covering both proofs the
815+ // three former branches tested separately, so reuse is one condition.
816+ if ( ! options . forceRestart && hashMatches && currentBrokerHealthy ) {
821817 return true ;
822818 }
823819 // Raw Nous OAuth stays out of durable ~/.nemoclaw state. If the broker is
0 commit comments