{"record":{"id":"8b560400102e56b0","repo":"paperclipai/paperclip","slug":"invalid-status-rawstatus-must-be-one-of-v","errorCode":null,"errorMessage":"Invalid status '${rawStatus}'. Must be one of: ${validStatuses.join(\", \")}","messagePattern":"Invalid status '(.+?)'\\. Must be one of: (.+?)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/src/routes/plugins.ts","lineNumber":2558,"sourceCode":"   */\n  router.get(\"/plugins/:pluginId/jobs\", async (req, res) => {\n    assertBoardOrgAccess(req);\n    if (!jobDeps) {\n      res.status(501).json({ error: \"Job scheduling is not enabled\" });\n      return;\n    }\n\n    const { pluginId } = req.params;\n    const plugin = await resolvePlugin(registry, pluginId);\n    if (!plugin) {\n      res.status(404).json({ error: \"Plugin not found\" });\n      return;\n    }\n\n    const rawStatus = req.query.status as string | undefined;\n    const validStatuses = [\"active\", \"paused\", \"failed\"];\n    if (rawStatus !== undefined && !validStatuses.includes(rawStatus)) {\n      res.status(400).json({\n        error: `Invalid status '${rawStatus}'. Must be one of: ${validStatuses.join(\", \")}`,\n      });\n      return;\n    }\n\n    try {\n      const jobs = await jobDeps.jobStore.listJobs(\n        plugin.id,\n        rawStatus as \"active\" | \"paused\" | \"failed\" | undefined,\n      );\n      res.json(jobs);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      res.status(500).json({ error: message });\n    }\n  });\n\n  /**","sourceCodeStart":2540,"sourceCodeEnd":2576,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L2540-L2576","documentation":"Returned as HTTP 400 by GET /api/plugins/:pluginId/jobs when the optional ?status= query filter is present but not one of the allowed values active, paused, failed. The message lists the accepted values verbatim.","triggerScenarios":"Passing ?status=running, ?status=succeeded, ?status=completed, or any free-text value; passing multiple comma-joined statuses like active,paused.","commonSituations":"Assuming job statuses mirror run statuses (e.g. 'succeeded'); copying status vocabulary from another API; UI dropdown fed from the wrong enum source.","solutions":["Use exactly one of: active, paused, failed","Omit ?status entirely to list all jobs","Fix the client-side enum that feeds the filter to the three supported values"],"exampleFix":"// before\nGET /api/plugins/scheduler/jobs?status=running\n// after\nGET /api/plugins/scheduler/jobs?status=active","handlingStrategy":"validation","validationCode":"const JOB_STATUSES = ['active', 'paused', 'failed'] as const;\ntype JobStatus = typeof JOB_STATUSES[number];\nfunction isJobStatus(v: string): v is JobStatus { return (JOB_STATUSES as readonly string[]).includes(v); }\nif (status !== undefined && !isJobStatus(status)) throw new Error(`status must be one of: ${JOB_STATUSES.join(', ')}`);","typeGuard":"const JOB_STATUSES = ['active', 'paused', 'failed'] as const;\ntype JobStatus = typeof JOB_STATUSES[number];\nfunction isJobStatus(v: string): v is JobStatus {\n  return (JOB_STATUSES as readonly string[]).includes(v);\n}","tryCatchPattern":"try { await listPluginJobs(pluginId, status); } catch (e) { if (e.status === 400 && /Invalid status/.test(e.body.error)) fallbackToAllJobs(); else throw e; }","preventionTips":["Drive filter dropdowns from a shared constant matching the API enum","Never invent status values from run-state vocabulary","Omit the filter rather than guessing when unsure"],"tags":["http-400","query-params","plugins","jobs","enum-validation"],"backgroundTag":"invalid-query-parameter","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}