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
- Pass the current heartbeatRunId whenever reusesReusableLeaseId is set
- If there is genuinely no run, drop reusesReusableLeaseId and take the normal acquisition path instead
- Propagate run context through the calling service so the id reaches environmentService
- 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
- Thread heartbeatRunId through the run context so every lease call inside a run has it
- When calling outside a run, never set reusesReusableLeaseId
- Write an integration test for the heartbeat reacquire path
- Validate inputs at the route/service boundary before deeper calls
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
- A reusable lease handoff requires an execution workspace and
- A reusable lease cannot be replaced and reacquired in the sa
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- "tool" is required and must be a string
- "runContext" is required and must be an object
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/32a91cde3f4a7555.
Report an issue: GitHub.