paperclipai/paperclip · error

${message}

Error message

${message}

What it means

The catch-all around lifecycle.unload on DELETE /api/plugins/:pluginId, returned as HTTP 400 with the raw lifecycle error text. The dominant message is "Plugin <key> is already uninstalled. Use removeData=true to permanently delete it." — raised when the row already has status "uninstalled" and you DELETE again without purge. Other sources are cleanupInstallArtifacts or registry.uninstall failures (filesystem or DB errors); worker-stop failures are best-effort and logged, not thrown.

Source

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

    const plugin = await resolvePlugin(registry, pluginId);
    if (!plugin) {
      res.status(404).json({ error: "Plugin not found" });
      return;
    }

    try {
      const result = await lifecycle.unload(plugin.id, purge);
      await logPluginMutationActivity(req, "plugin.uninstalled", plugin.id, {
        pluginId: plugin.id,
        pluginKey: plugin.pluginKey,
        purge,
      });
      publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: plugin.id, action: "uninstalled" } });
      res.json(result);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      res.status(400).json({ error: message });
    }
  });

  /**
   * POST /api/plugins/:pluginId/enable
   *
   * Enable a plugin that is currently disabled or in error state.
   *
   * Transitions the plugin to 'ready' state after loading and validation.
   *
   * Response: PluginRecord
   * Errors: 404 if plugin not found, 400 for lifecycle errors
   */
  router.post("/plugins/:pluginId/enable", async (req, res) => {
    assertInstanceAdmin(req);
    assertPluginManagementVisible();
    const { pluginId } = req.params;

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. If the message says "already uninstalled", append ?purge=true to hard-delete the row (without purge, soft-deleted data is retained ~30 days).
  2. Treat 400 + "already uninstalled" as an idempotent success in automation instead of a failure.
  3. For filesystem/DB messages, check server logs for the cleanup stack trace and fix the underlying disk/permission/DB issue before retrying.

Example fix

// before
await fetch(`/api/plugins/${id}`, { method: "DELETE" });

// after — hard-delete when a previous uninstall already soft-deleted it
await fetch(`/api/plugins/${id}?purge=true`, { method: "DELETE" });
Defensive patterns

Strategy: try-catch

Validate before calling

async function uninstallPlugin(apiBase: string, id: string): Promise<void> {
  const res = await fetch(`${apiBase}/api/plugins/${id}`, { method: "DELETE" });
  if (res.status === 400) {
    const { error } = await res.json();
    if (/already uninstalled/i.test(error)) {
      await fetch(`${apiBase}/api/plugins/${id}?purge=true`, { method: "DELETE" });
      return;
    }
    throw new Error(error);
  }
  if (!res.ok) throw new Error(`uninstall failed: ${res.status}`);
}

Try / catch

try {
  await api.uninstall(id, { purge: false });
} catch (err) {
  if (err instanceof HttpError && err.status === 400 && /already uninstalled/i.test(err.message)) {
    await api.uninstall(id, { purge: true }); // desired end state is full removal
  } else {
    throw err; // filesystem/db failure — needs operator attention, not retry
  }
}

Prevention

When it happens

Trigger: DELETE /api/plugins/:id on a plugin whose status is already "uninstalled" without ?purge=true (double uninstall); install-artifact directory missing or locked on disk when cleanup runs; DB error during soft/hard delete.

Common situations: UI double-fire or retry of an uninstall that already succeeded; CI script with delete-then-delete steps; artifacts directory manually removed so cleanup fails.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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