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
- Check service status and logs: `paperclipai service status`, `paperclipai service logs -n 200`.
- If migrations/init are slow, increase patience or pre-warm the DB; consider a longer timeout in the caller.
- If using `--expected-version`, confirm it matches the actually deployed server version, or omit it.
- 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
- Pre-warm the database (run migrations) before restart to cut boot time.
- Omit --expected-version unless you truly need version pinning.
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
- Another restart for instance ${instanceId} is still running.
- Cannot restart ${status.serviceName}: supervisor did not rep
- ${detection.reason}
- CLI auth challenge expired before approval.
- Import request returned no data.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/2b3cdfdb94fe4bd0.
Report an issue: GitHub.