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
- If the message says "already uninstalled", append ?purge=true to hard-delete the row (without purge, soft-deleted data is retained ~30 days).
- Treat 400 + "already uninstalled" as an idempotent success in automation instead of a failure.
- 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
- Read the 400 message before retrying — 'already uninstalled' and cleanup failures need opposite responses.
- Decide purge semantics up front and pass ?purge=true consistently when hard delete is intended.
- Guard uninstall buttons against double-submission to avoid the second-call error entirely.
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
- Plugin is not ready (current status: ${plugin.status})
- Unable to resolve company for plugin API route
- Plugin UI is not available (status: ${plugin.status})
- Invalid file path
- devUiUrl must use http or https protocol
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18).
Data as JSON: /api/errors/6c24b6551b7afb91.
Report an issue: GitHub.