Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import fs from 'fs';
import path from 'path';
import http from 'http';
import {
MessageJobData,
getSettings, getAgents, getTeams, LOG_FILE, FILES_DIR,
Expand Down Expand Up @@ -214,7 +215,13 @@ function logAgentConfig(): void {

initQueueDb();

const apiServer = startApiServer();
let apiServer: http.Server;
try {
apiServer = startApiServer();
} catch (error) {
log('ERROR', `Failed to start API server: ${(error as Error).message}`);
process.exit(1);
}
Comment on lines +218 to +224
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@hono/node-server serve function error handling EADDRINUSE

💡 Result:

The EADDRINUSE error in @hono/node-server's serve function occurs when attempting to bind to a port (default 3000) that is already in use by another process. This is a standard Node.js http.Server listen error, not specific to Hono. To handle it: 1. Identify and kill the process using the port: - macOS/Linux: lsof -i :3000 then kill -9 - Windows: netstat -ano | findstr :3000 then taskkill /PID /F - Or kill all Node processes: killall node (macOS/Linux) or taskkill /F /IM node.exe (Windows) 2. Use a different port: import { serve } from '@hono/node-server' import { Hono } from 'hono' const app = new Hono serve({ fetch: app.fetch, port: 3001 // or process.env.PORT || 3000 }) 3. For graceful handling, capture the returned server and add an error listener: const server = serve({ fetch: app.fetch, port: 3000 }) server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(Port 3000 is already in use. Try a different port.) process.exit(1) } }) No built-in EADDRINUSE handling exists in serve options per official docs. Hono's app.onError handles route-level errors, not server bind errors.

Citations:


🏁 Script executed:

# First, let's examine the code around lines 218-224 in the target file
cd packages/main/src && head -230 index.ts | tail -20

Repository: Maliot100X/ZooBot

Length of output: 584


🏁 Script executed:

# Also check the imports and find the startApiServer function definition
rg "startApiServer|import.*startApiServer" packages/main/src/index.ts -B 2 -A 2

Repository: Maliot100X/ZooBot

Length of output: 366


🏁 Script executed:

# Find where startApiServer is defined
rg "function startApiServer|const startApiServer|export.*startApiServer" packages/main/src/ -A 10

Repository: Maliot100X/ZooBot

Length of output: 43


🏁 Script executed:

# Check what startApiServer returns and how it uses serve()
rg "serve\(" packages/main/src/ -B 3 -A 3

Repository: Maliot100X/ZooBot

Length of output: 43


🏁 Script executed:

# Find the server package
ls -la packages/ | grep -i server

Repository: Maliot100X/ZooBot

Length of output: 116


🏁 Script executed:

# Search for startApiServer in the entire codebase
rg "export.*startApiServer" --type ts --type js

Repository: Maliot100X/ZooBot

Length of output: 137


🏁 Script executed:

# Try to find server-related files
fd "server" packages/ --type f -e ts | head -20

Repository: Maliot100X/ZooBot

Length of output: 43


🏁 Script executed:

# Read the startApiServer implementation
cat -n packages/server/src/index.ts | grep -A 20 "export function startApiServer"

Repository: Maliot100X/ZooBot

Length of output: 827


🏁 Script executed:

# Read more of the startApiServer implementation
cat -n packages/server/src/index.ts | sed -n '45,100p'

Repository: Maliot100X/ZooBot

Length of output: 2236


Add error handler for async port binding failures.

The serve() function from @hono/node-server returns synchronously, but the actual port binding happens asynchronously. The try/catch only catches synchronous errors like invalid port validation from resolveApiPort().

If the port is already in use (EADDRINUSE), the error will emit as an 'error' event on the server object and won't be caught by this try/catch, causing an unhandled exception.

🔧 Proposed fix
 let apiServer: http.Server;
 try {
     apiServer = startApiServer();
+    apiServer.on('error', (err: NodeJS.ErrnoException) => {
+        log('ERROR', `API server error: ${err.message}`);
+        if (err.code === 'EADDRINUSE') {
+            log('ERROR', `Port already in use`);
+        }
+        process.exit(1);
+    });
 } catch (error) {
     log('ERROR', `Failed to start API server: ${(error as Error).message}`);
     process.exit(1);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let apiServer: http.Server;
try {
apiServer = startApiServer();
} catch (error) {
log('ERROR', `Failed to start API server: ${(error as Error).message}`);
process.exit(1);
}
let apiServer: http.Server;
try {
apiServer = startApiServer();
apiServer.on('error', (err: NodeJS.ErrnoException) => {
log('ERROR', `API server error: ${err.message}`);
if (err.code === 'EADDRINUSE') {
log('ERROR', `Port already in use`);
}
process.exit(1);
});
} catch (error) {
log('ERROR', `Failed to start API server: ${(error as Error).message}`);
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/main/src/index.ts` around lines 218 - 224, The code currently
assigns apiServer = startApiServer() but only wraps synchronous errors; attach
an 'error' event handler to the returned server (apiServer) to catch async
port-binding failures (e.g., EADDRINUSE) and log the error via log('ERROR', ...)
and exit(1) as needed; update the start-up sequence around
startApiServer()/serve() so that after apiServer is returned you call
apiServer.on('error', handler) that inspects err.code and handles the failure
(logging and process.exit(1)) to prevent an unhandled exception.


// Event-driven: process queue when a new message arrives
queueEvents.on('message:enqueued', () => processQueue());
Expand Down
13 changes: 10 additions & 3 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ import agentMessagesRoutes from './routes/agent-messages';
import servicesRoutes from './routes/services';
import schedulesRoutes from './routes/schedules';

const API_PORT = parseInt(process.env.ZOOBOT_API_PORT || '3777', 10);
export function resolveApiPort(): number {
const raw = process.env.ZOOBOT_API_PORT || '3777';
const port = parseInt(raw, 10);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`Invalid ZOOBOT_API_PORT: ${raw}`);
}
return port;
}

/**
* Create and start the API server.
Expand Down Expand Up @@ -84,9 +91,9 @@ export function startApiServer(): http.Server {

const server = serve({
fetch: app.fetch,
port: API_PORT,
port: resolveApiPort(),
}, () => {
log('INFO', `API server listening on http://localhost:${API_PORT}`);
log('INFO', `API server listening on http://localhost:${resolveApiPort()}`);
});

return server as unknown as http.Server;
Expand Down
2 changes: 1 addition & 1 deletion zoobot-office/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"scripts": {
"dev": "next dev --webpack",
"build": "next build",
"start": "next start",
"start": "node .next/standalone/zoobot-office/server.js",
"lint": "eslint"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion zoobot-office/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const DEFAULT_API_BASE = "http://localhost:3777";
const DEFAULT_API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3777";
const STORAGE_KEY = "zoobot_api_base";

/** Resolve the API base URL. Priority: env > localStorage > default. */
Expand Down
Loading