{"record":{"id":"e63dd07ff90a9920","repo":"Mintplex-Labs/anything-llm","slug":"internal-server-error-e63dd0","errorCode":null,"errorMessage":"Internal Server Error","messagePattern":"Internal Server Error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"server/endpoints/scheduledJobs.js","lineNumber":27,"sourceCode":"// BackgroundService is a singleton, so `new BackgroundService()` anywhere in\n// the codebase returns the same instance that `server/index.js` booted. We\n// grab that reference once and reuse it across handlers.\nconst backgroundService = new BackgroundService();\n\nfunction scheduledJobEndpoints(app) {\n  if (!app) return;\n\n  // List available tools for job configuration\n  app.get(\n    \"/scheduled-jobs/available-tools\",\n    [validatedRequest, isSingleUserMode],\n    async (_request, response) => {\n      try {\n        const tools = await ScheduledJob.availableTools();\n        return response.status(200).json({ tools });\n      } catch (e) {\n        console.error(e.message, e);\n        response.sendStatus(500).json({ tools: [] });\n      }\n    }\n  );\n\n  // Get a single run detail\n  app.get(\n    \"/scheduled-jobs/runs/:runId\",\n    [validatedRequest, isSingleUserMode],\n    async (request, response) => {\n      try {\n        const run = await ScheduledJobRun.get({\n          id: Number(request.params.runId),\n        });\n        if (!run) {\n          return response\n            .status(404)\n            .json({ run: null, error: \"Run not found\" });\n        }","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/526360e320da9d1b36074be5ed64fe76e5bbfbbd/server/endpoints/scheduledJobs.js#L9-L45","documentation":"HTTP 500 from GET /scheduled-jobs/available-tools. The handler calls ScheduledJob.availableTools() and returns the list. Note a secondary bug: the catch calls response.sendStatus(500).json({ tools: [] }) — Express's sendStatus sends the status code and ends the response, so the chained .json() call either throws or is silently ignored, meaning the client never receives the {tools:[]} fallback body. The 500 itself is triggered when availableTools() throws.","triggerScenarios":"GET /scheduled-jobs/available-tools when ScheduledJob.availableTools() throws — e.g., it dynamically discovers agent tools and a tool registration fails, or it reads from a config/DB table that is missing; the endpoint is hit before the BackgroundService singleton has finished initializing; the tool registry includes a plugin that throws on enumeration.","commonSituations":"Fresh deployment where the tools registry hasn't been seeded; a custom tool plugin that throws during discovery; DB migration not run so the underlying tools table doesn't exist; version upgrade that changed the tool discovery interface.","solutions":["Check the server log for e.message — it will show which tool or DB query failed.","Fix the response bug: use response.status(500).json({ tools: [] }) instead of sendStatus(500).json(...) so the fallback body is actually sent.","Ensure ScheduledJob.availableTools() has a try-catch internally that returns [] on failure rather than throwing.","Run database migrations and verify the BackgroundService singleton initialized at boot."],"exampleFix":"// before — sendStatus ends the response; .json() is dead code\n} catch (e) {\n  console.error(e.message, e);\n  response.sendStatus(500).json({ tools: [] });\n}\n\n// after — use status() so the json body is actually sent\n} catch (e) {\n  console.error(e.message, e);\n  response.status(500).json({ tools: [] });\n}","handlingStrategy":"fallback","validationCode":"// This is a GET endpoint with no user-supplied body to validate.\n// The only pre-check is ensuring the BackgroundService is initialized.\nif (!backgroundService || typeof backgroundService.isReady === 'function' && !backgroundService.isReady())\n  return response.status(200).json({ tools: [] });","typeGuard":null,"tryCatchPattern":"// Fix the sendStatus().json() bug AND make availableTools non-throwing.\ntry {\n  const tools = await ScheduledJob.availableTools();\n  return response.status(200).json({ tools });\n} catch (e) {\n  console.error('available-tools failed:', e.message, e);\n  // Use status() not sendStatus() so the json body is actually sent\n  return response.status(200).json({ tools: [] }); // degrade gracefully — empty list, not an error\n}","preventionTips":["Never chain .json() after sendStatus() — sendStatus ends the response. Use response.status(code).json(obj) instead.","Make ScheduledJob.availableTools() internally catch errors and return [] rather than throwing — a tool discovery failure should not crash the endpoint.","Consider returning 200 with {tools:[]} instead of 500 — the frontend can still render the page without the tools list.","Log the error even when falling back, so the root cause isn't hidden."],"tags":["express","scheduled-jobs","tool-discovery","sendstatus-bug","dead-code"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}