{"record":{"id":"f33d7c649806cafd","repo":"Mintplex-Labs/anything-llm","slug":"tools-must-be-an-array","errorCode":null,"errorMessage":"Tools must be an array","messagePattern":"Tools must be an array","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/endpoints/scheduledJobs.js","lineNumber":160,"sourceCode":"    [validatedRequest, isSingleUserMode],\n    async (request, response) => {\n      try {\n        const { name, prompt, tools, schedule } = reqBody(request);\n        let errorMessage = null;\n\n        if (!name?.trim()) {\n          errorMessage = \"Name is required\";\n        } else if (!prompt?.trim()) {\n          errorMessage = \"Prompt is required\";\n        } else if (!schedule?.trim()) {\n          errorMessage = \"Schedule is required\";\n        } else if (!ScheduledJob.isValidCron(schedule)) {\n          errorMessage = \"Invalid cron expression\";\n        } else if (tools?.length > 0 && !Array.isArray(tools)) {\n          errorMessage = \"Tools must be an array\";\n        }\n        if (errorMessage)\n          return response.status(400).json({\n            job: null,\n            error: errorMessage,\n          });\n\n        // New jobs default to enabled, so creating one always counts as an\n        // activation. Reject if it would push us past the configured cap.\n        const activation = await ScheduledJob.canActivate();\n        if (!activation.allowed) {\n          return response.status(400).json({\n            job: null,\n            error: `Cannot create: maximum of ${activation.limit} active scheduled jobs reached. Disable another job first.`,\n          });\n        }\n\n        const { job, error } = await ScheduledJob.create({\n          name: name.trim(),\n          prompt: prompt.trim(),\n          tools: tools || null,","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/endpoints/scheduledJobs.js#L142-L178","documentation":"Validation error from POST /api/scheduled-jobs in AnythingLLM's scheduled-jobs API. The optional `tools` field must be a JSON array of agent tool identifiers (or omitted/null); the guard fires only when the value is truthy, has length > 0, and is not an Array. In practice a non-empty string was sent instead of an array, because strings have a .length property and pass the first half of the check.","triggerScenarios":"POST /api/scheduled-jobs with body {\"name\":\"Daily report\",\"prompt\":\"...\",\"schedule\":\"0 9 * * *\",\"tools\":\"rag-search\"} — tools as a plain string instead of [\"rag-search\"]. Also triggered by comma-separated values such as \"rag-search,web-browse\" from a form input or custom client.","commonSituations":"HTML form / query-string serialization collapsing a single-element array to a scalar; scripts written against an older free-form tools field; passing a variable that is conditionally a string; copy-pasting one tool name without array brackets.","solutions":["Send tools as a JSON array of strings, e.g. \"tools\": [\"rag-search\"], or omit the field entirely when no tools are needed","Fix the client serializer so single-element arrays stay arrays (proper JSON body, FormData.getAll instead of .get)","When building from free-form input, split CSV into an array before sending: tools.trim().split(',').map(t => t.trim()).filter(Boolean)"],"exampleFix":"// before\nawait fetch('/api/scheduled-jobs', {\n  method: 'POST',\n  body: JSON.stringify({ name, prompt, schedule, tools: 'rag-search' })\n});\n\n// after\nawait fetch('/api/scheduled-jobs', {\n  method: 'POST',\n  body: JSON.stringify({ name, prompt, schedule, tools: ['rag-search'] })\n});","handlingStrategy":"validation","validationCode":"// Before POST /api/scheduled-jobs\nconst tools = rawTools == null ? null : rawTools;\nif (tools !== null && !Array.isArray(tools)) {\n  throw new Error(`tools must be an array of strings, got ${typeof tools}`);\n}\nawait fetch('/api/scheduled-jobs', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ name, prompt, schedule, tools })\n});","typeGuard":"function isToolList(v) {\n  return v == null || (Array.isArray(v) && v.every(t => typeof t === 'string' && t.length > 0));\n}","tryCatchPattern":null,"preventionTips":["Always JSON.stringify the request body (never send form-encoded values for tools)","Normalize CSV or single tool names to arrays at the API boundary of your client","Add a client-side unit test asserting tools is an array for every create path"],"tags":["api","validation","scheduled-jobs","request-body"],"backgroundTag":"request-body-validation","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}