paperclipai/paperclip · error

Plugin worker is not running

Error message

Plugin worker is not running

What it means

Returned as HTTP 503 when the plugin's database status is "ready" but workerManager.isRunning(plugin.id) reports the worker process is not running. The DB record and runtime have diverged: the worker crashed, was stopped out-of-band, or was never respawned after a host restart. If the workerManager has no isRunning function the check is skipped (assumed running), so this error specifically means a real isRunning() probe returned false.

Source

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

      res.status(501).json({ error: "Plugin scoped API routes are not enabled" });
      return;
    }

    const { pluginId } = req.params;
    const plugin = await resolvePlugin(registry, pluginId);
    if (!plugin) {
      res.status(404).json({ error: "Plugin not found" });
      return;
    }
    if (plugin.status !== "ready") {
      res.status(503).json({ error: `Plugin is not ready (current status: ${plugin.status})` });
      return;
    }
    const isWorkerRunning = typeof bridgeDeps.workerManager.isRunning === "function"
      ? bridgeDeps.workerManager.isRunning(plugin.id)
      : true;
    if (!isWorkerRunning) {
      res.status(503).json({ error: "Plugin worker is not running" });
      return;
    }
    if (!plugin.manifestJson.capabilities.includes("api.routes.register")) {
      res.status(404).json({ error: "Plugin does not expose scoped API routes" });
      return;
    }

    const requestPath = req.path || "/";
    const routes = plugin.manifestJson.apiRoutes ?? [];
    const match = routes
      .map((route) => ({ route, params: matchScopedApiRoute(route, req.method, requestPath) }))
      .find((candidate) => candidate.params !== null);
    if (!match || !match.params) {
      res.status(404).json({ error: "Plugin API route not found" });
      return;
    }

    try {

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Retry after a short delay — if the loader supports runtime activation it may respawn the worker on its next pass.
  2. Force a worker restart via a disable/enable cycle: POST /api/plugins/:id/disable then POST /api/plugins/:id/enable (only valid from ready -> disabled -> ready).
  3. Run GET /api/plugins/:pluginId/health and check the worker check plus server logs for the crash cause (missing config, bad entrypoint, missing dependency).
  4. Fix the underlying worker crash (see the plugin's logs via GET /api/plugins/:pluginId/logs?level=error).

Example fix

// before
const res = await fetch(`/api/plugins/${id}/api/issues`);
if (res.status === 503) throw new Error("plugin API failed");

// after
const res = await fetch(`/api/plugins/${id}/api/issues`);
if (res.status === 503) {
  await fetch(`/api/plugins/${id}/disable`, { method: "POST", headers, body: JSON.stringify({ reason: "worker not running" }) });
  await fetch(`/api/plugins/${id}/enable`, { method: "POST", headers });
}
Defensive patterns

Strategy: retry

Validate before calling

async function pluginWorkerRunning(apiBase: string, pluginId: string): Promise<boolean> {
  const res = await fetch(`${apiBase}/api/plugins/${encodeURIComponent(pluginId)}/health`);
  if (!res.ok) return false;
  const health = await res.json();
  return health.checks?.every((c: { passed: boolean }) => c.passed) ?? false;
}

Type guard

interface HealthCheck { name: string; passed: boolean; message: string }
function allChecksPassed(checks: HealthCheck[]): boolean {
  return Array.isArray(checks) && checks.length > 0 && checks.every((c) => c.passed === true);
}

Try / catch

let lastErr: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
  try { return await callScopedApi(pluginId, path); }
  catch (err) {
    lastErr = err;
    if (!(err instanceof HttpError) || err.status !== 503 || !/worker is not running/i.test(err.message)) throw err;
    await delay(2 ** attempt * 500); // worker may respawn
  }
}
throw lastErr;

Prevention

When it happens

Trigger: Calling /api/plugins/:pluginId/api/* when the worker child process died (OOM kill, unhandled worker exception) without the status flipping to "error", or during the window after a server restart where the DB row still says ready but the loader has not re-spawned workers yet.

Common situations: Worker crash-loop from a bad plugin entrypoint; container restart where PG data (status=ready) survives but processes do not; worker manually killed by an operator; memory limits reaping the child process.

Related errors


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