paperclipai/paperclip · error
Another restart for instance ${instanceId} is still running.
Error message
Another restart for instance ${instanceId} is still running. If no restart process is active, remove the stale lock at ${lockPath} and retry. What it means
Thrown by withHotRestartLock() when it cannot acquire the `hot-restart.lock` file within the 120s deadline. The lock is held by another restart (pid alive) or is stale but the pid-alive check could not clear it. The message tells the operator the lock path so they can intervene.
Source
Thrown at cli/src/commands/service.ts:97
await fs.writeFile(lockPath, `${token}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
break;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
try {
const existingToken = (await fs.readFile(lockPath, "utf8")).trim();
const ownerPid = Number.parseInt(existingToken.split(":", 1)[0] ?? "", 10);
if (Number.isInteger(ownerPid) && ownerPid > 0 && !isProcessAlive(ownerPid)) {
if ((await fs.readFile(lockPath, "utf8")).trim() === existingToken) {
await fs.rm(lockPath, { force: true });
continue;
}
}
} catch (readError) {
if ((readError as NodeJS.ErrnoException).code === "ENOENT") continue;
throw readError;
}
if (Date.now() >= deadline) {
throw new Error(
`Another restart for instance ${instanceId} is still running. ` +
`If no restart process is active, remove the stale lock at ${lockPath} and retry.`,
);
}
await new Promise((resolve) => setTimeout(resolve, pollMs));
}
}
try {
return await callback();
} finally {
try {
if ((await fs.readFile(lockPath, "utf8")).trim() === token) {
await fs.rm(lockPath, { force: true });
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}View on GitHub (pinned to 67001ec6eb)
Solutions
- Confirm no restart is actually running: `ps aux | grep paperclip` and check the pid in the lock file.
- If none is active, remove the stale lock: `rm <lockPath>` (path is in the error message), then retry.
- Serialize restarts in automation with your own mutex so they never overlap.
- If using `--wait` for drain, allow more time or drain manually before restart.
Example fix
# before: overlapping restarts error paperclipai service restart # after: clear stale lock and serialize rm "$HOME/.paperclip/<instance>/hot-restart.lock" paperclipai service restart
Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'node:fs';
function lockStaleOrHeld(lockPath: string): boolean {
if (!fs.existsSync(lockPath)) return false;
const pid = Number(fs.readFileSync(lockPath, 'utf8').split(':')[0]);
try { process.kill(pid, 0); return true; } catch { return false; /* stale */ }
} Try / catch
try {
await withHotRestartLock(instanceId, () => doRestart());
} catch (err) {
if (/stale lock/.test(String(err))) {
await fs.promises.rm(lockPath, {force: true});
// retry once
} else throw err;
} Prevention
- Serialize restart calls in automation with your own lock/mutex.
- After a crashed restart, clean the lock file before the next run.
When it happens
Trigger: Two concurrent `service restart` calls for the same instance, or a previous restart process is still alive, or a stale lock whose owner pid still appears alive (zombie/reused pid). The acquire loop fails `O_EXCL` write for >120s.
Common situations: Automation firing overlapping restarts; a crashed restart that left a lock whose pid was reused by an unrelated process; a long-running drain (`--wait`) exceeding the lock deadline.
Related errors
- Paperclip service did not become healthy${expectedVersion ?
- Cannot restart ${status.serviceName}: supervisor did not rep
- ${detection.reason}
- Worktree seed lock ${lockPath} belongs to exited process ${c
- Worktree seed lock ${lockPath} is stale or malformed. Verify
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/6c20fd0d45712c6b.
Report an issue: GitHub.