@@ -885,6 +885,36 @@ function getLogLinesForResponse(url) {
885885 return lines ;
886886}
887887
888+ /** Reference to the HTTP server so we can close it on shutdown. */
889+ let apiServer = null ;
890+ let shuttingDown = false ;
891+
892+ /**
893+ * Graceful shutdown: close the HTTP server (releases the port), then exit.
894+ * Called on SIGTERM/SIGINT so restarts don't fight over the port.
895+ */
896+ function gracefulShutdown ( signal ) {
897+ if ( shuttingDown ) return ;
898+ shuttingDown = true ;
899+ logInfo ( `🛑 received ${ signal } — shutting down gracefully` ) ;
900+ if ( apiServer ) {
901+ apiServer . close ( ( ) => {
902+ logInfo ( "🛑 HTTP server closed, exiting" ) ;
903+ process . exit ( 0 ) ;
904+ } ) ;
905+ // Force exit after 5s if connections don't drain
906+ setTimeout ( ( ) => {
907+ logWarn ( "🛑 forceful exit after 5s timeout" ) ;
908+ process . exit ( 1 ) ;
909+ } , 5000 ) . unref ( ) ;
910+ } else {
911+ process . exit ( 0 ) ;
912+ }
913+ }
914+
915+ process . on ( "SIGTERM" , ( ) => gracefulShutdown ( "SIGTERM" ) ) ;
916+ process . on ( "SIGINT" , ( ) => gracefulShutdown ( "SIGINT" ) ) ;
917+
888918function startApiServer ( ) {
889919 const server = createServer ( async ( req , res ) => {
890920 const url = new URL ( req . url , `http://localhost:${ API_PORT } ` ) ;
@@ -1024,9 +1054,33 @@ function startApiServer() {
10241054 }
10251055 } ) ;
10261056
1027- server . listen ( API_PORT , "127.0.0.1" , ( ) => {
1057+ // Retry with backoff if the port is still held by a dying predecessor.
1058+ const MAX_BIND_RETRIES = 5 ;
1059+ const BIND_RETRY_DELAY_MS = 2000 ;
1060+ let bindAttempt = 0 ;
1061+
1062+ function tryListen ( ) {
1063+ bindAttempt ++ ;
1064+ server . listen ( API_PORT , "127.0.0.1" ) ;
1065+ }
1066+
1067+ server . on ( "listening" , ( ) => {
1068+ apiServer = server ;
10281069 logInfo ( `📡 Outbound API listening on http://127.0.0.1:${ API_PORT } ` ) ;
10291070 } ) ;
1071+
1072+ server . on ( "error" , ( err ) => {
1073+ if ( err . code === "EADDRINUSE" && bindAttempt < MAX_BIND_RETRIES ) {
1074+ logWarn ( `⚠️ port ${ API_PORT } in use, retrying in ${ BIND_RETRY_DELAY_MS } ms (attempt ${ bindAttempt } /${ MAX_BIND_RETRIES } )` ) ;
1075+ server . close ( ) ;
1076+ setTimeout ( tryListen , BIND_RETRY_DELAY_MS ) ;
1077+ } else {
1078+ logError ( `❌ HTTP server error: ${ err . message } ` ) ;
1079+ process . exit ( 1 ) ;
1080+ }
1081+ } ) ;
1082+
1083+ tryListen ( ) ;
10301084}
10311085
10321086async function startPollLoop ( ) {
0 commit comments