From 2f13aa644ebd660e845f6295ca11289ad5bd7d65 Mon Sep 17 00:00:00 2001 From: NicoAgHerrera <132090401+NicoAgHerrera@users.noreply.github.com> Date: Sun, 31 May 2026 11:59:04 -0300 Subject: [PATCH 1/2] Handle ingress port conflicts during startup --- scripts/dev-with-automation.mjs | 32 +++++++++------ scripts/ingress.mjs | 69 ++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/scripts/dev-with-automation.mjs b/scripts/dev-with-automation.mjs index 72581d8ab..c150ccbc4 100644 --- a/scripts/dev-with-automation.mjs +++ b/scripts/dev-with-automation.mjs @@ -662,7 +662,7 @@ function startIngress(config) { const ingressScript = join(projectRoot, "scripts", "ingress.mjs"); - spawnService( + return spawnService( "ingress", "node", [ @@ -979,7 +979,21 @@ async function main(options = {}) { // Start services phase logStep("2/2", "Starting services..."); - // 1. Start agent-server first (other services depend on it) + // 1. Start ingress first so port conflicts fail before spawning + // the heavier backend/frontend services. + const ingressProcess = startIngress(config); + + // Wait briefly for ingress to either start listening or fail fast. + await delay(1000); + + if (!isProcessRunning(ingressProcess)) { + logError( + `Ingress failed to start on port ${config.ingressPort}. See the ingress logs above for details.`, + ); + process.exit(1); + } + + // 2. Start agent-server first (other services depend on it) const agentServerStarter = startAgentServerOverride ?? startAgentServer; agentServerStarter(config); @@ -990,7 +1004,7 @@ async function main(options = {}) { 60000, // 60 second timeout for initial startup ); - // 2. Seed automation API key into agent-server secrets + // 3. Seed automation API key into agent-server secrets // This makes the key available to agents during conversations // Note: seedAutomationSecret has its own retry logic if server is still warming up if (agentServerReady) { @@ -1003,25 +1017,19 @@ async function main(options = {}) { ); } - // 3. Start automation backend + // 4. Start automation backend startAutomationBackend(config); - // 4. Start frontend server (Vite dev server OR static server) + // 5. Start frontend server (Vite dev server OR static server) if (useStaticMode) { startStaticFrontend(config, staticDir); } else { startVite(config); } - // 5. Wait for services to be ready + // 6. Wait for services to be ready await delay(2000); - // 6. Start ingress proxy (routes traffic to all backends) - startIngress(config); - - // Wait for ingress to start - await delay(1000); - printBanner(config); } diff --git a/scripts/ingress.mjs b/scripts/ingress.mjs index d32dfea09..bef9d7b4f 100644 --- a/scripts/ingress.mjs +++ b/scripts/ingress.mjs @@ -128,12 +128,16 @@ function buildConfig(args, env = process.env) { function createRouter(routes, defaultBackend) { // Sort routes by path length (longest first) for most-specific matching const sortedRoutes = Object.entries(routes).sort( - ([a], [b]) => b.length - a.length + ([a], [b]) => b.length - a.length, ); return function route(url) { for (const [prefix, backend] of sortedRoutes) { - if (url === prefix || url.startsWith(prefix + "/") || url.startsWith(prefix + "?")) { + if ( + url === prefix || + url.startsWith(prefix + "/") || + url.startsWith(prefix + "?") + ) { return backend; } } @@ -253,7 +257,10 @@ function proxyWebSocket(req, socket, head, backendUrl) { // TCP socket would crash the entire ingress process. socket.on("error", (err) => { if (!isBenignSocketError(err)) { - console.error(`WebSocket client socket error for ${req.url}:`, err.message); + console.error( + `WebSocket client socket error for ${req.url}:`, + err.message, + ); } proxyReq.destroy(); }); @@ -278,10 +285,12 @@ function proxyWebSocket(req, socket, head, backendUrl) { proxySocket.on("close", () => socket.destroy()); socket.write( - `HTTP/${proxyRes.httpVersion} ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n` + `HTTP/${proxyRes.httpVersion} ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n`, ); for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) { - socket.write(`${proxyRes.rawHeaders[i]}: ${proxyRes.rawHeaders[i + 1]}\r\n`); + socket.write( + `${proxyRes.rawHeaders[i]}: ${proxyRes.rawHeaders[i + 1]}\r\n`, + ); } socket.write("\r\n"); @@ -347,17 +356,41 @@ function startIngress(config) { } }); + server.on("error", (err) => { + if (err.code === "EADDRINUSE") { + console.error( + `Cannot start ingress: port ${config.port} is already in use.\n` + + "Stop the process using that port, or choose another port with --port.", + ); + process.exit(1); + } + + throw err; + }); + server.listen(config.port, () => { console.log(""); - console.log("╔═══════════════════════════════════════════════════════════════╗"); - console.log("║ Ingress Proxy ║"); - console.log("╠═══════════════════════════════════════════════════════════════╣"); - console.log(`║ Listening on: http://localhost:${config.port}/`.padEnd(66) + "║"); - console.log("╠═══════════════════════════════════════════════════════════════╣"); - console.log("║ Routes: ║"); + console.log( + "╔═══════════════════════════════════════════════════════════════╗", + ); + console.log( + "║ Ingress Proxy ║", + ); + console.log( + "╠═══════════════════════════════════════════════════════════════╣", + ); + console.log( + `║ Listening on: http://localhost:${config.port}/`.padEnd(66) + "║", + ); + console.log( + "╠═══════════════════════════════════════════════════════════════╣", + ); + console.log( + "║ Routes: ║", + ); const sortedRoutes = Object.entries(config.routes).sort( - ([a], [b]) => b.length - a.length + ([a], [b]) => b.length - a.length, ); for (const [path, backend] of sortedRoutes) { const line = ` ${path} → ${backend}`; @@ -369,8 +402,12 @@ function startIngress(config) { console.log(`║ ${line.padEnd(61)}║`); } - console.log("║ ║"); - console.log("╚═══════════════════════════════════════════════════════════════╝"); + console.log( + "║ ║", + ); + console.log( + "╚═══════════════════════════════════════════════════════════════╝", + ); console.log(""); }); @@ -385,7 +422,9 @@ const args = parseArgs(); const config = buildConfig(args); if (Object.keys(config.routes).length === 0 && !config.defaultBackend) { - console.error("Error: No routes configured. Use --route or --default options."); + console.error( + "Error: No routes configured. Use --route or --default options.", + ); console.error("Run with --help for usage information."); process.exit(1); } From 047b15930ac2c73d543eaf991ca77201557e2d48 Mon Sep 17 00:00:00 2001 From: NicoAgHerrera <132090401+NicoAgHerrera@users.noreply.github.com> Date: Sun, 31 May 2026 12:35:56 -0300 Subject: [PATCH 2/2] Remove unrelated ingress formatting changes --- scripts/ingress.mjs | 59 ++++++++++++--------------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/scripts/ingress.mjs b/scripts/ingress.mjs index bef9d7b4f..b819889e9 100644 --- a/scripts/ingress.mjs +++ b/scripts/ingress.mjs @@ -128,16 +128,12 @@ function buildConfig(args, env = process.env) { function createRouter(routes, defaultBackend) { // Sort routes by path length (longest first) for most-specific matching const sortedRoutes = Object.entries(routes).sort( - ([a], [b]) => b.length - a.length, + ([a], [b]) => b.length - a.length ); return function route(url) { for (const [prefix, backend] of sortedRoutes) { - if ( - url === prefix || - url.startsWith(prefix + "/") || - url.startsWith(prefix + "?") - ) { + if (url === prefix || url.startsWith(prefix + "/") || url.startsWith(prefix + "?")) { return backend; } } @@ -257,10 +253,7 @@ function proxyWebSocket(req, socket, head, backendUrl) { // TCP socket would crash the entire ingress process. socket.on("error", (err) => { if (!isBenignSocketError(err)) { - console.error( - `WebSocket client socket error for ${req.url}:`, - err.message, - ); + console.error(`WebSocket client socket error for ${req.url}:`, err.message); } proxyReq.destroy(); }); @@ -285,12 +278,10 @@ function proxyWebSocket(req, socket, head, backendUrl) { proxySocket.on("close", () => socket.destroy()); socket.write( - `HTTP/${proxyRes.httpVersion} ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n`, + `HTTP/${proxyRes.httpVersion} ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n` ); for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) { - socket.write( - `${proxyRes.rawHeaders[i]}: ${proxyRes.rawHeaders[i + 1]}\r\n`, - ); + socket.write(`${proxyRes.rawHeaders[i]}: ${proxyRes.rawHeaders[i + 1]}\r\n`); } socket.write("\r\n"); @@ -370,27 +361,15 @@ function startIngress(config) { server.listen(config.port, () => { console.log(""); - console.log( - "╔═══════════════════════════════════════════════════════════════╗", - ); - console.log( - "║ Ingress Proxy ║", - ); - console.log( - "╠═══════════════════════════════════════════════════════════════╣", - ); - console.log( - `║ Listening on: http://localhost:${config.port}/`.padEnd(66) + "║", - ); - console.log( - "╠═══════════════════════════════════════════════════════════════╣", - ); - console.log( - "║ Routes: ║", - ); + console.log("╔═══════════════════════════════════════════════════════════════╗"); + console.log("║ Ingress Proxy ║"); + console.log("╠═══════════════════════════════════════════════════════════════╣"); + console.log(`║ Listening on: http://localhost:${config.port}/`.padEnd(66) + "║"); + console.log("╠═══════════════════════════════════════════════════════════════╣"); + console.log("║ Routes: ║"); const sortedRoutes = Object.entries(config.routes).sort( - ([a], [b]) => b.length - a.length, + ([a], [b]) => b.length - a.length ); for (const [path, backend] of sortedRoutes) { const line = ` ${path} → ${backend}`; @@ -402,12 +381,8 @@ function startIngress(config) { console.log(`║ ${line.padEnd(61)}║`); } - console.log( - "║ ║", - ); - console.log( - "╚═══════════════════════════════════════════════════════════════╝", - ); + console.log("║ ║"); + console.log("╚═══════════════════════════════════════════════════════════════╝"); console.log(""); }); @@ -422,9 +397,7 @@ const args = parseArgs(); const config = buildConfig(args); if (Object.keys(config.routes).length === 0 && !config.defaultBackend) { - console.error( - "Error: No routes configured. Use --route or --default options.", - ); + console.error("Error: No routes configured. Use --route or --default options."); console.error("Run with --help for usage information."); process.exit(1); } @@ -451,4 +424,4 @@ process.on("SIGINT", () => { process.on("SIGTERM", () => { console.log("\nShutting down..."); process.exit(0); -}); +}); \ No newline at end of file