{"record":{"id":"f56c80fed9e45242","repo":"actualbudget/actual","slug":"internal-error-f56c80","errorCode":"internal-error","errorMessage":"internal-error","messagePattern":"internal-error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"critical","filePath":"packages/sync-server/src/util/middlewares.ts","lineNumber":30,"sourceCode":") {\n  if (res.headersSent) {\n    // If you call next() with an error after you have started writing the response\n    // (for example, if you encounter an error while streaming the response\n    // to the client), the Express default error handler closes\n    // the connection and fails the request.\n\n    // So when you add a custom error handler, you must delegate\n    // to the default Express error handler, when the headers\n    // have already been sent to the client\n    // Source: https://expressjs.com/en/guide/error-handling.html\n    return next(err);\n  }\n\n  console.log(`Error on endpoint %s`, {\n    requestUrl: req.url,\n    stacktrace: err.stack,\n  });\n  res.status(500).send({ status: 'error', reason: 'internal-error' });\n}\n\nconst validateSessionMiddleware = async (\n  req: Request,\n  res: Response,\n  next: NextFunction,\n) => {\n  const session = await validateSession(req, res);\n  if (!session) {\n    return;\n  }\n\n  res.locals = session;\n  next();\n};\n\nconst requestLoggerMiddleware = expressWinston.logger({\n  transports: [new winston.transports.Console()],","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/util/middlewares.ts#L12-L48","documentation":"errorMiddleware is the sync-server's catch-all Express error handler: any unhandled error in a route is logged ('Error on endpoint %s' with url and stacktrace) and converted into a 500 response { status: 'error', reason: 'internal-error' }. The client never sees the real cause — the details are on the server logs only.","triggerScenarios":"Any route throwing/rejecting outside its own try/catch: database failures (sqlite corruption/locked), unhandled exceptions in handlers, JSON body parse errors surfacing late, errors in async middleware (e.g. session validation) not caught upstream.","commonSituations":"Corrupt or locked better-sqlite3 database on self-hosted servers; missing/misconfigured environment variables failing lazily; bugs in custom forks/plugins; transient filesystem errors in endpoints lacking their own error handling.","solutions":["Check the sync-server logs for 'Error on endpoint <url>' and read the stacktrace — that is the real cause of the 500.","Verify server config/env (account db path, userFiles dir, secrets) and that the sqlite database file is intact and writable.","Retry the request after fixing the server-side cause; if persistent, restore the database from backup.","If you own the code, wrap route handlers (or rely on errorMiddleware) and add targeted logging for the failing endpoint."],"exampleFix":"// before (server log only)\n// Error on endpoint /sync ... stacktrace: SqliteError: database is locked\n// after: resolve the cause, e.g. single-writer access or enable WAL\n// db.pragma('journal_mode = WAL');","handlingStrategy":"try-catch","validationCode":"// client-side: check server health before calling\nconst health = await fetch(base + '/health');\nif (!health.ok) throw new Error('sync server unhealthy');","typeGuard":null,"tryCatchPattern":"const res = await callSyncEndpoint();\nif (res.status === 500 && (await res.json()).reason === 'internal-error') {\n  // details are server-side: log request id/url, retry once after backoff, then escalate to server logs\n  await new Promise(r => setTimeout(r, 1000));\n  retryOrEscalate();\n}","preventionTips":["Monitor sync-server logs for 'Error on endpoint' lines — that is where the real stacktrace lives.","Keep the sqlite database on reliable storage and take regular backups.","Pin well-tested server versions; test env-var changes in staging.","Add per-endpoint error handling in any custom server code so errors are specific, not generic 500s."],"tags":["server","internal","unhandled-exception","http"],"backgroundTag":"internal-server-error","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}