Skip to content

Harden API port handling and startup logging#2

Open
Maliot100X wants to merge 2 commits intomainfrom
tinyagi/runtime-hardening-1
Open

Harden API port handling and startup logging#2
Maliot100X wants to merge 2 commits intomainfrom
tinyagi/runtime-hardening-1

Conversation

@Maliot100X
Copy link
Copy Markdown
Owner

@Maliot100X Maliot100X commented Mar 24, 2026

PR Title

Description

Changes

Testing

Checklist

  • PR title follows conventional commit format (type(scope): description)
  • I have tested these changes locally
  • My changes don't introduce new warnings or errors
  • I have updated documentation if needed

Summary by CodeRabbit

  • New Features

    • API configuration now supports environment variables to customize the server port and API base URL for flexible deployments.
  • Bug Fixes

    • Improved server startup error handling with clearer failure messages and safer shutdown behavior on initialization errors.
  • Chores

    • Production start method updated to run the built standalone server directly.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07091f9a-6fe5-4af6-9d84-ebe35cc2f4f0

📥 Commits

Reviewing files that changed from the base of the PR and between 3f170b8 and ac4fa36.

📒 Files selected for processing (1)
  • zoobot-office/package.json

📝 Walkthrough

Walkthrough

Adds env-driven API host/port configuration, validates port values, wraps API server startup in try/catch with process exit on failure, adjusts shutdown to handle an optionally-uninitialized server, and changes the Next.js start script to run the standalone server binary.

Changes

Cohort / File(s) Summary
Main app startup
packages/main/src/index.ts
Added http import; declare let apiServer: http.Server; wrap startApiServer() in try/catch with error logging and process.exit(1) on failure; adjusted shutdown to call apiServer.close().
Server port resolution & validation
packages/server/src/index.ts
Replaced module-level API_PORT with exported resolveApiPort() that reads process.env.ZOOBOT_API_PORT, parses and validates it (1–65535); startup and log now call resolveApiPort().
Client API base selection
zoobot-office/src/lib/api.ts
Changed DEFAULT_API_BASE to use `process.env.NEXT_PUBLIC_API_URL
Production start script
zoobot-office/package.json
Replaced next start with direct Node invocation of the Next.js standalone server: node .next/standalone/zoobot-office/server.js.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled env vars, hopped the port,

Caught a crash before it could cavort,
Built a path for API calls to run,
Launched the server—now look, it's fun! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description consists entirely of the template with no substantive content filled in. The Changes and Testing sections contain only '-' placeholders, and no explanation of the modifications or validation approach is provided. Fill in the Description, Changes, and Testing sections with details about the port validation, error handling improvements, and how these changes were tested.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden API port handling and startup logging' accurately describes the main changes: adding port validation in packages/server/src/index.ts and improving error handling in packages/main/src/index.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tinyagi/runtime-hardening-1

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/server/src/index.ts (1)

92-97: Resolve port once to avoid redundant calls.

resolveApiPort() is called twice—once for the server config and once for the log message. While functionally harmless, this is slightly inefficient and could theoretically diverge if the environment were modified between calls.

♻️ Proposed refactor to resolve port once
+    const port = resolveApiPort();
     const server = serve({
         fetch: app.fetch,
-        port: resolveApiPort(),
+        port,
     }, () => {
-        log('INFO', `API server listening on http://localhost:${resolveApiPort()}`);
+        log('INFO', `API server listening on http://localhost:${port}`);
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/index.ts` around lines 92 - 97, Call resolveApiPort()
once and reuse the value for both the serve() port option and the log message to
avoid duplicate calls and potential divergence; assign the result to a local
constant (e.g., const port = resolveApiPort()) before creating server
(serve({... port }) ) and then use that same port variable in the log('INFO',
...) call, referencing the existing server creation block and the resolveApiPort
function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/main/src/index.ts`:
- Around line 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.

---

Nitpick comments:
In `@packages/server/src/index.ts`:
- Around line 92-97: Call resolveApiPort() once and reuse the value for both the
serve() port option and the log message to avoid duplicate calls and potential
divergence; assign the result to a local constant (e.g., const port =
resolveApiPort()) before creating server (serve({... port }) ) and then use that
same port variable in the log('INFO', ...) call, referencing the existing server
creation block and the resolveApiPort function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7adfe42c-dcfb-42de-ac3c-3eac89f1376d

📥 Commits

Reviewing files that changed from the base of the PR and between 6ffd776 and 3f170b8.

📒 Files selected for processing (3)
  • packages/main/src/index.ts
  • packages/server/src/index.ts
  • zoobot-office/src/lib/api.ts

Comment on lines +218 to +224
let apiServer: http.Server;
try {
apiServer = startApiServer();
} catch (error) {
log('ERROR', `Failed to start API server: ${(error as Error).message}`);
process.exit(1);
}
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant