Fix crash loop: handle HTTP parse errors from keep-alive monitoring agents

Monitored services that send data after Connection: close (malformed HTTP)
were causing unhandled 'Parse Error' exceptions, crashing the process and
triggering a PM2 restart loop (191 restarts observed) which caused 502s.

Two-layer fix:
- monitorService.js: attach .on('error', noop) to the shared HTTP/HTTPS
  keep-alive agents so socket-level parse errors don't escape as uncaught
  exceptions
- index.js: process-level uncaughtException guard that swallows HTTP parse
  errors (HPE_INVALID_CONSTANT / 'Parse Error') while still crashing on
  real fatal errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa
This commit is contained in:
Claude committed 2026-08-06 23:54:00 +00:00
1 parent 42f224ab93
commit b13a8c343f
2 files changed
+17 -1

No files matched your search

+11
View File
@@ -6,6 +6,17 @@ const http = require('http');
const { randomUUID } = require('crypto');
require('dotenv').config();
// Prevent HTTP parser errors (e.g. from keep-alive connections to monitored
// services that send data after Connection: close) from crashing the process.
process.on('uncaughtException', (err) => {
if (err.code === 'HPE_INVALID_CONSTANT' || (err.message && err.message.includes('Parse Error'))) {
console.error('HTTP parse error (swallowed):', err.message);
return;
}
console.error('Uncaught exception:', err);
process.exit(1);
});
const db = require('./database');
const scheduler = require('./scheduler');
const monitorService = require('./monitorService');
+6 -1
View File
@@ -31,9 +31,14 @@ function parseHostPort(urlString, fallbackPort) {
class MonitorService {
constructor() {
// Keep-alive agents for pooled HTTP/S connections
// Keep-alive agents for pooled HTTP/S connections.
// Socket-level 'error' listener prevents parse errors (e.g. "Data after
// Connection: close") from bubbling up as uncaught exceptions when a
// monitored server sends a malformed response on a reused connection.
this.httpAgent = new http.Agent({ keepAlive: true, maxSockets: 50 });
this.httpAgent.on('error', () => {});
this.httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 50 });
this.httpsAgent.on('error', () => {});
// Circuit breaker for HTTP checks (5 failures before opening, 60s reset)
this.httpCircuitBreaker = new CircuitBreaker(5, 60000, this.isTransientError);