{"record":{"id":"fb4ee268277efdcc","repo":"paperclipai/paperclip","slug":"invalid-status-string-rawstatus-must-be-one","errorCode":null,"errorMessage":"Invalid status '${String(rawStatus)}'. Must be one of: ${PLUGIN_STATUSES.join(\", \")}","messagePattern":"Invalid status '(.+?)'\\. Must be one of: (.+?)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/src/routes/plugins.ts","lineNumber":859,"sourceCode":"   * GET /api/plugins\n   *\n   * List all installed plugins, optionally filtered by lifecycle status.\n   *\n   * Query params:\n   * - `status` (optional): Filter by lifecycle status. Must be one of the\n   *   values in `PLUGIN_STATUSES` (`installed`, `ready`, `error`,\n   *   `upgrade_pending`, `uninstalled`). Returns HTTP 400 if the value is\n   *   not a recognised status string.\n   *\n   * Response: `PluginRecord[]`\n   */\n  router.get(\"/plugins\", async (req, res) => {\n    assertBoardOrgAccess(req);\n    const rawStatus = req.query.status;\n    if (rawStatus !== undefined) {\n      if (typeof rawStatus !== \"string\" || !(PLUGIN_STATUSES as readonly string[]).includes(rawStatus)) {\n        res.status(400).json({\n          error: `Invalid status '${String(rawStatus)}'. Must be one of: ${PLUGIN_STATUSES.join(\", \")}`,\n        });\n        return;\n      }\n    }\n    const status = rawStatus as PluginStatus | undefined;\n    const plugins = status\n      ? await registry.listByStatus(status)\n      : await registry.listInstalled();\n    res.json(plugins);\n  });\n\n  /**\n   * GET /api/plugins/examples\n   *\n   * Return plugin packages bundled in this repo, if present.\n   * These can be installed through the normal local-path install flow.\n   */\n  router.get(\"/plugins/examples\", async (req, res) => {","sourceCodeStart":841,"sourceCodeEnd":877,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L841-L877","documentation":"Returned as HTTP 400 by GET /api/plugins (server/src/routes/plugins.ts:844) when the optional ?status= query parameter is present but is not one of the PLUGIN_STATUSES values: 'installed', 'ready', 'disabled', 'error', 'upgrade_pending', 'uninstalled'. The route validates before calling registry.listByStatus; note the code's own JSDoc omits 'disabled' but the shared constant includes it, so trust the constant's list.","triggerScenarios":"GET /api/plugins?status=active, ?status=READY (case-sensitive), ?status[]=ready (array from repeated params, fails the typeof string check), or any typo like 'erorr'. Each returns 400 with the accepted values listed in the message.","commonSituations":"Client code written against older/assumed status vocabularies ('enabled', 'active'); case mismatches; query builders that serialize arrays for singular params; stale API clients after new statuses ('disabled', 'upgrade_pending') were added to PLUGIN_STATUSES.","solutions":["Use one of the exact lowercase values: installed, ready, disabled, error, upgrade_pending, uninstalled","Import PLUGIN_STATUSES from @paperclipai/shared in client code and validate/build the query from that constant instead of hardcoding strings","Ensure the query serializer emits a single string value (no repeated ?status= params)"],"exampleFix":"// before\nconst res = await fetch(`/api/plugins?status=${filter}`); // filter = 'active'\n\n// after\nimport { PLUGIN_STATUSES } from \"@paperclipai/shared\";\nconst status = PLUGIN_STATUSES.includes(filter) ? filter : undefined;\nconst res = await fetch(`/api/plugins${status ? `?status=${status}` : \"\"}`);","handlingStrategy":"validation","validationCode":"import { PLUGIN_STATUSES } from \"@paperclipai/shared\";\nconst qs = status && (PLUGIN_STATUSES as readonly string[]).includes(status)\n  ? `?status=${encodeURIComponent(status)}`\n  : \"\";\nconst res = await fetch(`/api/plugins${qs}`);","typeGuard":"import { PLUGIN_STATUSES, type PluginStatus } from \"@paperclipai/shared\";\nconst isPluginStatus = (s: string): s is PluginStatus =>\n  (PLUGIN_STATUSES as readonly string[]).includes(s);","tryCatchPattern":null,"preventionTips":["Derive filter values from PLUGIN_STATUSES instead of hardcoding status strings","Keep status values lowercase — matching is case-sensitive","Send ?status= exactly once; repeated params produce an array and always 400"],"tags":["plugins","query-params","validation","http-400","rest-api"],"backgroundTag":"query-param-validation","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}