paperclipai/paperclip · error

A same-run reusable lease reacquisition requires a heartbeat

Error message

A same-run reusable lease reacquisition requires a heartbeat run id.

What it means

When reusing an existing reusable lease within the same run (reusesReusableLeaseId set), the operation must include heartbeatRunId so the reacquisition is attributable and scoped to the current run. It throws when a same-run reacquisition is requested without that run id.

Source

Thrown at server/src/services/environments.ts:1395

        lastUsedAt: now,
        expiresAt: input.expiresAt ?? null,
        releasedAt: null,
        failureReason: null,
        cleanupStatus: null,
        metadata: input.metadata ?? null,
        createdAt: now,
        updatedAt: now,
      };
      if (
        (input.replacesReusableLeaseId || input.reusesReusableLeaseId) &&
        (!input.executionWorkspaceId || !input.providerLeaseId)
      ) {
        throw new Error(
          "A reusable lease handoff requires an execution workspace and provider lease id.",
        );
      }
      if (input.reusesReusableLeaseId && !input.heartbeatRunId) {
        throw new Error(
          "A same-run reusable lease reacquisition requires a heartbeat run id.",
        );
      }
      if (input.replacesReusableLeaseId && input.reusesReusableLeaseId) {
        throw new Error(
          "A reusable lease cannot be replaced and reacquired in the same operation.",
        );
      }
      const row =
        input.assertCompanyBinding ||
        input.replacesReusableLeaseId ||
        input.reusesReusableLeaseId
          ? await db.transaction(async (tx) => {
              if (input.assertCompanyBinding) {
                // Lock the environment row first. Managed reconciliation locks the
                // same sandbox environment rows with `for update` before it writes a
                // company binding, so this lock serializes the two transactions on
                // this row and closes the time-of-check to time-of-use window.

View on GitHub (pinned to 01ad858492)

Solutions

  1. Pass the current heartbeatRunId whenever reusesReusableLeaseId is set
  2. If there is genuinely no run, drop reusesReusableLeaseId and take the normal acquisition path instead
  3. Propagate run context through the calling service so the id reaches environmentService
  4. Check heartbeat wiring to confirm the reacquire call happens inside an active run

Example fix

// before
reacquireLease({ reusesReusableLeaseId: leaseId })
// after
reacquireLease({ reusesReusableLeaseId: leaseId, heartbeatRunId: run.id })
Defensive patterns

Strategy: validation

Validate before calling

if (input.reusesReusableLeaseId && !input.heartbeatRunId) {
  throw new Error('same-run reacquisition requires heartbeatRunId');
}
await reacquireLease(input);

Type guard

function isSameRunReacquire(i: {reusesReusableLeaseId?:string;heartbeatRunId?:string|null}): boolean {
  return !i.reusesReusableLeaseId || Boolean(i.heartbeatRunId);
}

Try / catch

try {
  await environmentService.reacquireLease(input);
} catch (e) {
  if (e.message.includes('heartbeat run id')) {
    if (currentRun) return environmentService.reacquireLease({ ...input, heartbeatRunId: currentRun.id });
    return environmentService.acquireLease({ ...input, reusesReusableLeaseId: undefined }); // fall back to fresh acquire
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling environmentService lease reacquisition with reusesReusableLeaseId but heartbeatRunId missing/undefined — typically a heartbeat-driven reacquire path that lost its run context.

Common situations: Caller invoked outside a heartbeat run context (manual script or retry path) but still flagged as same-run reuse; run id not propagated through the service layer after refactor; test harness omitting heartbeatRunId.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/32a91cde3f4a7555. Report an issue: GitHub.