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

  1. Confirm no restart is actually running: `ps aux | grep paperclip` and check the pid in the lock file.
  2. If none is active, remove the stale lock: `rm <lockPath>` (path is in the error message), then retry.
  3. Serialize restarts in automation with your own mutex so they never overlap.
  4. 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

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


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