abhigyanpatwari/GitNexus · critical
Internal server error
Error message
Internal server error
What it means
HTTP 500 emitted by the global Express error handler mounted after every route — the catch-all for exceptions route handlers and middleware did not absorb (notably express.json() parse failures, which this handler reports as 500 even though body-parser types them as 400). The real error is logged server-side as 'Unhandled error:' with the full object; the response body deliberately exposes no internals.
Source
Thrown at gitnexus/src/server/api.ts:2047
embedJobManager.cancelJob(jobId, 'Cancelled by user');
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
});
// ── Web UI (served at root) ───────────────────────────────────────
// Resolve the gitnexus-web dist directory relative to this file's location.
// In the published package: <pkg>/dist/server/api.js → <pkg>/web/
// In dev (tsx): gitnexus/src/server/api.ts → gitnexus-web/dist/
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const webDistDir = path.resolve(__dirname, '..', '..', 'web');
const devWebDistDir = path.resolve(__dirname, '..', '..', '..', 'gitnexus-web', 'dist');
const staticDir = await resolveWebDistDir(webDistDir, devWebDistDir);
registerWebUI(app, staticDir);
// Global error handler — catch anything the route handlers miss
app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
logger.error({ err }, 'Unhandled error:');
res.status(500).json({ error: 'Internal server error' });
});
// Wrap listen in a promise so errors (EADDRINUSE, EACCES, etc.) propagate
// to the caller instead of crashing with an unhandled 'error' event.
await new Promise<void>((resolve, reject) => {
const server = app.listen(port, host, () => {
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
console.log(`GitNexus server running on http://${displayHost}:${port}`);
resolve();
});
server.on('error', (err) => reject(err));
// `listening` is the successful startup boundary for notifier work.
bindServeUpdateControllerLifecycle(server, updateController);
// Graceful shutdown — close Express + LadybugDB cleanly. Pino's default
// destination is `sync: false` (buffered); `flushLoggerSync()` before
// `process.exit` so records emitted during cleanup reach stderr.
const shutdown = async () => {View on GitHub (pinned to 0d1aed942f)
Solutions
- Read the serve logs — the 'Unhandled error:' line has the stack; the client-visible body never will
- Build bodies with JSON.stringify and send Content-Type: application/json
- Retry once for potentially transient causes (races, broken streams); stop if the failure is deterministic
- A deterministic reproduction reaching this handler is a server bug — update GitNexus and report it with the logged stack
Example fix
// before
const body = "{'url': 'https://github.com/org/repo'}"; // hand-written, invalid JSON
await fetch('/api/analyze', { method: 'POST', body });
// after
const body = JSON.stringify({ url: 'https://github.com/org/repo' });
await fetch('/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
}); Defensive patterns
Strategy: retry
Validate before calling
// Fail fast on hand-built JSON before sending
function safeJsonParse(s: string): unknown {
try { return JSON.parse(s); } catch { throw new TypeError('Request body is not valid JSON'); }
} Try / catch
Catch 500s at the fetch boundary: log the server-side correlation (timestamp + route), retry idempotent requests once with backoff, and report deterministic cases — the response body carries no diagnostic detail by design.
Prevention
- Never hand-write JSON bodies — use JSON.stringify
- Set Content-Type: application/json on all JSON endpoints
- Watch the serve logs when debugging 500s; the response body is sanitized
When it happens
Trigger: Malformed JSON body (trailing commas, single quotes, unescaped newlines) reaching express.json(); an unexpected throw inside any route handler; middleware-level failures such as oversized or mis-declared bodies; errors re-thrown past a route's own try/catch.
Common situations: Hand-built request strings instead of JSON.stringify output; clients posting form-data to JSON-only endpoints; version-mismatch bugs; proxies mangling request bodies.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- Parameter "${fieldName}" must be a single string, got an arr
- Parameter "${fieldName}" must be a string
- Upload failed
- -32000
- Query failed
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/828fc553de5059d8.
Report an issue: GitHub.