paperclipai/paperclip · error

Paperclip service did not become healthy${expectedVersion ?

Error message

Paperclip service did not become healthy${expectedVersion ? ` at version ${expectedVersion}` : ""}: ${last.error ?? `reported ${last.serverVersion ?? "no version"}`}

What it means

Thrown by waitForHealth() when the service did not report healthy (status ok and, if expectedVersion is set, a matching version) within the 60s deadline. The message includes the last probe's error or reported version to aid diagnosis.

Source

Thrown at cli/src/commands/service.ts:50

async function probeHealth(instanceId: string): Promise<HealthResult> {
  try {
    const response = await fetch(healthUrl(instanceId), { signal: AbortSignal.timeout(2_000) });
    const body = await response.json() as { status?: unknown; serverVersion?: unknown; version?: unknown };
    return { ok: response.ok && body.status === "ok", serverVersion: typeof body.serverVersion === "string" ? body.serverVersion : typeof body.version === "string" ? body.version : null };
  } catch (error) {
    return { ok: false, serverVersion: null, error: error instanceof Error ? error.message : String(error) };
  }
}

async function waitForHealth(instanceId: string, expectedVersion: string | null, timeoutMs = 60_000): Promise<HealthResult> {
  const deadline = Date.now() + timeoutMs;
  let last: HealthResult = { ok: false, serverVersion: null };
  while (Date.now() < deadline) {
    last = await probeHealth(instanceId);
    if (last.ok && (!expectedVersion || last.serverVersion === expectedVersion)) return last;
    await new Promise((resolve) => setTimeout(resolve, 500));
  }
  throw new Error(`Paperclip service did not become healthy${expectedVersion ? ` at version ${expectedVersion}` : ""}: ${last.error ?? `reported ${last.serverVersion ?? "no version"}`}`);
}

export function resolveRestartExpectedVersion(expectedVersion: string | null | undefined): string | null {
  return expectedVersion ?? null;
}

export async function withHotRestartLock<T>(
  instanceId: string,
  callback: () => Promise<T>,
  options: { timeoutMs?: number; pollMs?: number; isProcessAlive?: (pid: number) => boolean } = {},
): Promise<T> {
  const instanceRoot = resolvePaperclipInstanceRoot(instanceId);
  const lockPath = path.join(instanceRoot, "hot-restart.lock");
  const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
  const deadline = Date.now() + (options.timeoutMs ?? 120_000);
  const pollMs = options.pollMs ?? 100;
  const isProcessAlive = options.isProcessAlive ?? ((pid: number) => {
    try {

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Check service status and logs: `paperclipai service status`, `paperclipai service logs -n 200`.
  2. If migrations/init are slow, increase patience or pre-warm the DB; consider a longer timeout in the caller.
  3. If using `--expected-version`, confirm it matches the actually deployed server version, or omit it.
  4. Verify the health endpoint is reachable: `curl http://127.0.0.1:<port>/api/health`.

Example fix

# before
paperclipai service restart --expected-version 1.2.3
# after (verify version, or drop the requirement)
paperclipai service status
curl http://127.0.0.1:3100/api/health
paperclipai service restart
Defensive patterns

Strategy: retry

Validate before calling

async function healthy(url: string, timeoutMs = 60_000): Promise<boolean> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    try { const r = await fetch(url); if (r.ok) return true; } catch {}
    await new Promise(r => setTimeout(r, 500));
  }
  return false;
}

Try / catch

try {
  await waitForHealth(instanceId, expectedVersion);
} catch (err) {
  console.error('Health timeout. Check service logs:', err);
  // inspect logs, then optionally retry once with longer timeout
}

Prevention

When it happens

Trigger: After `service restart` (or any caller of waitForHealth), the loop probes health every 500ms for 60s; if the server never returns `{status:"ok"}` at `/api/health`, or returns a version that does not match `--expected-version`, this throws.

Common situations: Server booting slowly (long migrations, heavy plugin init), server crashed on startup but the supervisor still shows active, wrong expected version after a deploy, health endpoint bound to a different port/host than probed.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/2b3cdfdb94fe4bd0. Report an issue: GitHub.