paperclipai/paperclip · warning

Invalid status '${String(rawStatus)}'. Must be one of: ${PLU

Error message

Invalid status '${String(rawStatus)}'. Must be one of: ${PLUGIN_STATUSES.join(", ")}

What it means

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.

Source

Thrown at server/src/routes/plugins.ts:859

   * GET /api/plugins
   *
   * List all installed plugins, optionally filtered by lifecycle status.
   *
   * Query params:
   * - `status` (optional): Filter by lifecycle status. Must be one of the
   *   values in `PLUGIN_STATUSES` (`installed`, `ready`, `error`,
   *   `upgrade_pending`, `uninstalled`). Returns HTTP 400 if the value is
   *   not a recognised status string.
   *
   * Response: `PluginRecord[]`
   */
  router.get("/plugins", async (req, res) => {
    assertBoardOrgAccess(req);
    const rawStatus = req.query.status;
    if (rawStatus !== undefined) {
      if (typeof rawStatus !== "string" || !(PLUGIN_STATUSES as readonly string[]).includes(rawStatus)) {
        res.status(400).json({
          error: `Invalid status '${String(rawStatus)}'. Must be one of: ${PLUGIN_STATUSES.join(", ")}`,
        });
        return;
      }
    }
    const status = rawStatus as PluginStatus | undefined;
    const plugins = status
      ? await registry.listByStatus(status)
      : await registry.listInstalled();
    res.json(plugins);
  });

  /**
   * GET /api/plugins/examples
   *
   * Return plugin packages bundled in this repo, if present.
   * These can be installed through the normal local-path install flow.
   */
  router.get("/plugins/examples", async (req, res) => {

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Use one of the exact lowercase values: installed, ready, disabled, error, upgrade_pending, uninstalled
  2. Import PLUGIN_STATUSES from @paperclipai/shared in client code and validate/build the query from that constant instead of hardcoding strings
  3. Ensure the query serializer emits a single string value (no repeated ?status= params)

Example fix

// before
const res = await fetch(`/api/plugins?status=${filter}`); // filter = 'active'

// after
import { PLUGIN_STATUSES } from "@paperclipai/shared";
const status = PLUGIN_STATUSES.includes(filter) ? filter : undefined;
const res = await fetch(`/api/plugins${status ? `?status=${status}` : ""}`);
Defensive patterns

Strategy: validation

Validate before calling

import { PLUGIN_STATUSES } from "@paperclipai/shared";
const qs = status && (PLUGIN_STATUSES as readonly string[]).includes(status)
  ? `?status=${encodeURIComponent(status)}`
  : "";
const res = await fetch(`/api/plugins${qs}`);

Type guard

import { PLUGIN_STATUSES, type PluginStatus } from "@paperclipai/shared";
const isPluginStatus = (s: string): s is PluginStatus =>
  (PLUGIN_STATUSES as readonly string[]).includes(s);

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18). Data as JSON: /api/errors/fb4ee268277efdcc. Report an issue: GitHub.