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

  1. Read the serve logs — the 'Unhandled error:' line has the stack; the client-visible body never will
  2. Build bodies with JSON.stringify and send Content-Type: application/json
  3. Retry once for potentially transient causes (races, broken streams); stop if the failure is deterministic
  4. 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

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

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20). Data as JSON: /api/errors/828fc553de5059d8. Report an issue: GitHub.