Author SHA1 Message Date
Claude dcfd9e9b65 Fix crash loop: disable keep-alive agents and add unhandledRejection guard
The previous fix (agent .on('error')) doesn't reach individual socket
objects — socket-level parse errors still escaped as uncaught exceptions.

Root fix: disable keepAlive on the HTTP/HTTPS monitoring agents. Without
connection reuse there is no stale connection to trigger "Parse Error:
Data after Connection: close". Monitoring intervals are 60s+ so the lack
of connection pooling has no meaningful performance impact.

Belt-and-suspenders: add process.on('unhandledRejection') alongside the
existing uncaughtException guard to catch any async paths that leak.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa
2026-08-07 00:02:01 +00:00
Claude b13a8c343f 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
2026-08-06 23:54:00 +00:00
Claude 42f224ab93 Merge remote-tracking branch 'origin/main' into claude/app-load-failure-db-rvlxbc 2026-08-06 23:53:21 +00:00
nmemmertandClaude 0b4797f38c Fix app startup crash: add hasInstallScript to better-sqlite3 lockfile entry (#1)
package-lock.json was missing "hasInstallScript": true for better-sqlite3.
npm ci reads this flag to decide whether to run a package's install scripts;
without it, the native .node binding is never compiled and the server crashes
at startup with "Cannot find module '...better_sqlite3.node'".

Running npm install regenerates the field correctly. npm ci (used in
update-server.sh) can now compile the native module during deployment.


Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-06 19:48:59 -04:00
Claude b710f68d21 Fix app startup crash: add hasInstallScript to better-sqlite3 lockfile entry
package-lock.json was missing "hasInstallScript": true for better-sqlite3.
npm ci reads this flag to decide whether to run a package's install scripts;
without it, the native .node binding is never compiled and the server crashes
at startup with "Cannot find module '...better_sqlite3.node'".

Running npm install regenerates the field correctly. npm ci (used in
update-server.sh) can now compile the native module during deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa
2026-08-06 23:46:22 +00:00
3 changed files with 29 additions and 3 deletions

No files matched your search

+1
View File
@@ -122,6 +122,7 @@
"version": "13.0.3", "version": "13.0.3",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz",
"integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"node-addon-api": "^8.0.0" "node-addon-api": "^8.0.0"
+20
View File
@@ -6,6 +6,26 @@ const http = require('http');
const { randomUUID } = require('crypto'); const { randomUUID } = require('crypto');
require('dotenv').config(); require('dotenv').config();
// Prevent HTTP parser errors (e.g. "Data after Connection: close") and other
// transient async errors 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);
});
process.on('unhandledRejection', (reason) => {
const msg = reason instanceof Error ? reason.message : String(reason);
if (msg.includes('Parse Error')) {
console.error('HTTP parse error (swallowed):', msg);
return;
}
console.error('Unhandled rejection:', reason);
});
const db = require('./database'); const db = require('./database');
const scheduler = require('./scheduler'); const scheduler = require('./scheduler');
const monitorService = require('./monitorService'); const monitorService = require('./monitorService');
+8 -3
View File
@@ -31,9 +31,14 @@ function parseHostPort(urlString, fallbackPort) {
class MonitorService { class MonitorService {
constructor() { constructor() {
// Keep-alive agents for pooled HTTP/S connections // Disable keep-alive to avoid "Parse Error: Data after Connection: close"
this.httpAgent = new http.Agent({ keepAlive: true, maxSockets: 50 }); // crashes. Keep-alive connection reuse causes the HTTP parser to throw
this.httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 50 }); // when a monitored server sends extra data after Connection: close — the
// socket-level error becomes an uncaught exception that kills the process.
// Monitoring checks are infrequent enough that connection pooling provides
// no meaningful benefit.
this.httpAgent = new http.Agent({ keepAlive: false });
this.httpsAgent = new https.Agent({ keepAlive: false });
// Circuit breaker for HTTP checks (5 failures before opening, 60s reset) // Circuit breaker for HTTP checks (5 failures before opening, 60s reset)
this.httpCircuitBreaker = new CircuitBreaker(5, 60000, this.isTransientError); this.httpCircuitBreaker = new CircuitBreaker(5, 60000, this.isTransientError);