paperclipai/paperclip · error · CreateosCleanupError
CreateOS process creation could not be confirmed; destroy…
Error message
CreateOS process creation could not be confirmed; destroy the lease before reusing it.
What it means
CreateosCleanupError thrown when a failure occurs after the creation request was sent but before the server confirmed the processId. Because the process may actually have started, the provider cannot safely let the caller reuse the lease — a subsequent process could collide with an orphan. The lease must be destroyed before reuse.
Solutions
- Destroy the sandbox lease before using it again — do not blindly retry commands on the same lease.
- Check the lease/sandbox via the API for orphaned processes before re-provisioning.
- Add retry logic at a higher level that creates a fresh lease instead of reusing this one.
- Reduce timeoutMs churn: ensure client timeout is long enough for create round-trips.
Example fix
// before
await execute(lease, cmd);
// after
try {
await execute(lease, cmd);
} catch (e) {
if (e instanceof CreateosCleanupError) await destroyLease(lease);
throw e;
} Defensive patterns
Strategy: try-catch
Type guard
function isCreateosCleanupError(e) {
return e instanceof CreateosCleanupError || e?.constructor?.name === "CreateosCleanupError";
} Try / catch
try {
await execute(lease, cmd, { signal });
} catch (e) {
if (isCreateosCleanupError(e)) {
await destroyLease(lease); // mandatory: process state unconfirmed
throw e;
}
throw e;
} Prevention
- Never reuse a lease after a CreateosCleanupError — always destroy it first.
- Set client timeoutMs high enough for create round-trips to complete.
- Wrap execute() in a helper that destroys the lease automatically on this error.
When it happens
Trigger: Any error (network failure, timeout, stream error) raised in execute() while creationMayHaveSucceeded is true and processId is still unset — i.e. the POST to create the process was sent, but the response with processId was never received.
Common situations: Connection dropped right after the create request; request timeout firing between send and response; server accepted the process but the response was lost.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- CreateOS lease cleanup must finish before resume.
- CreateOS command cleanup failed; process termination is…
- CreateOS command was cancelled.
- CreateOS does not yet support leases with a guaranteed…
- CreateOS execution requires a lease from this environment.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/2715d7d48ff9726c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/execute.ts:197
} else if (event.type === "error") {
throw new Error(event.error === "output_offset_expired"
? "CreateOS process output was evicted before it could be read."
: "CreateOS process stream reported an error.");
} else {
throw new Error("CreateOS returned an unknown process event.");
}
}
} catch (error) {
// Network read failures can resume from the last accepted sequence.
// Protocol errors must fail closed rather than reconnect past bad data.
if (!(error instanceof TypeError) || signal.aborted) throw error;
}
if (++reconnects > 3) throw new Error("CreateOS process stream ended without an exit status.");
await delay(250, undefined, { signal });
}
} catch (error) {
if (creationMayHaveSucceeded && !processId) {
throw new CreateosCleanupError("CreateOS process creation could not be confirmed; destroy the lease before reusing it.");
}
if (signal.aborted && signal.reason?.name === "TimeoutError") {
output.finish();
return {
exitCode: null, timedOut: true, stdout: output.stdout, stderr: output.stderr,
metadata: { processId, outputTruncated: output.truncated },
};
}
if (signal.aborted) throw new Error("CreateOS command was cancelled.");
throw error;
} finally {
const cleanupSignal = AbortSignal.timeout(client.config.timeoutMs);
// Do not hide a cleanup failure: the host must know containment is unproven.
try {
if (processId && !completed) {
try {
const termination = await client.json(`${base}/${processId}?grace_ms=1000`, "DELETE", undefined, cleanupSignal);
if (termination.tree_exited !== true) throw new Error("Process tree has not exited.");View on GitHub (pinned to 3f1d897a7c)