Mintplex-Labs/anything-llm · warning

Tools must be an array

Error message

Tools must be an array

What it means

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.

Source

Thrown at server/endpoints/scheduledJobs.js:160

    [validatedRequest, isSingleUserMode],
    async (request, response) => {
      try {
        const { name, prompt, tools, schedule } = reqBody(request);
        let errorMessage = null;

        if (!name?.trim()) {
          errorMessage = "Name is required";
        } else if (!prompt?.trim()) {
          errorMessage = "Prompt is required";
        } else if (!schedule?.trim()) {
          errorMessage = "Schedule is required";
        } else if (!ScheduledJob.isValidCron(schedule)) {
          errorMessage = "Invalid cron expression";
        } else if (tools?.length > 0 && !Array.isArray(tools)) {
          errorMessage = "Tools must be an array";
        }
        if (errorMessage)
          return response.status(400).json({
            job: null,
            error: errorMessage,
          });

        // New jobs default to enabled, so creating one always counts as an
        // activation. Reject if it would push us past the configured cap.
        const activation = await ScheduledJob.canActivate();
        if (!activation.allowed) {
          return response.status(400).json({
            job: null,
            error: `Cannot create: maximum of ${activation.limit} active scheduled jobs reached. Disable another job first.`,
          });
        }

        const { job, error } = await ScheduledJob.create({
          name: name.trim(),
          prompt: prompt.trim(),
          tools: tools || null,

View on GitHub (pinned to 3aec848f28)

Solutions

  1. Send tools as a JSON array of strings, e.g. "tools": ["rag-search"], or omit the field entirely when no tools are needed
  2. Fix the client serializer so single-element arrays stay arrays (proper JSON body, FormData.getAll instead of .get)
  3. When building from free-form input, split CSV into an array before sending: tools.trim().split(',').map(t => t.trim()).filter(Boolean)

Example fix

// before
await fetch('/api/scheduled-jobs', {
  method: 'POST',
  body: JSON.stringify({ name, prompt, schedule, tools: 'rag-search' })
});

// after
await fetch('/api/scheduled-jobs', {
  method: 'POST',
  body: JSON.stringify({ name, prompt, schedule, tools: ['rag-search'] })
});
Defensive patterns

Strategy: validation

Validate before calling

// Before POST /api/scheduled-jobs
const tools = rawTools == null ? null : rawTools;
if (tools !== null && !Array.isArray(tools)) {
  throw new Error(`tools must be an array of strings, got ${typeof tools}`);
}
await fetch('/api/scheduled-jobs', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name, prompt, schedule, tools })
});

Type guard

function isToolList(v) {
  return v == null || (Array.isArray(v) && v.every(t => typeof t === 'string' && t.length > 0));
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18). Data as JSON: /api/errors/f33d7c649806cafd. Report an issue: GitHub.