paperclipai/paperclip · error
ACPX provider ownership admission is closed
Error message
ACPX provider ownership admission is closed
What it means
SpawnedChildSet.beginLifetimeOwnershipAdmission() opens a window in which newly spawned ACPX provider children can be tracked and verified. The seal flags enforce a strict lifecycle: admission may only begin after a previous admission round was sealed via verifyLifetimeOwnership(), and never after the set was fully sealed for cleanup. Calling it while the set is #sealed (post-cleanup) throws 'ACPX provider ownership admission is closed'. It is a lifecycle invariant protecting against spawning provider work after teardown has begun.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts:1501
const ownership = this.#lifetimeOwnership.splice(0);
if (ownership.length === 0) {
// This check and seal are synchronous. Any spawn added while an
// earlier batch was pending is observed by the next loop iteration;
// no later provider can race admission after the stable-empty point.
this.#lifetimeOwnershipSealed = true;
return;
}
await Promise.all(ownership);
}
} catch (error) {
this.#lifetimeOwnershipSealed = true;
throw error;
}
}
beginLifetimeOwnershipAdmission(): () => Promise<void> {
if (this.#sealed) {
throw new Error("ACPX provider ownership admission is closed");
}
if (!this.#lifetimeOwnershipSealed) {
throw new Error("ACPX provider ownership admission is already active");
}
this.#lifetimeOwnershipSealed = false;
let finished = false;
return async () => {
if (finished) return;
finished = true;
await this.verifyLifetimeOwnership();
};
}
#track(child: ChildProcess, providerExit: ProviderExitObservation): void {
this.#children.add(child);
const onError = (error: unknown) => this.#errors.add(error);
let guardianExited = !running(child);
let providerExited = false;View on GitHub (pinned to 01ad858492)
Solutions
- Do not call startTurn after runtime cleanup has begun; check the runtime's closed/sealed state before submitting turns.
- Serialize turn submission against shutdown so startTurn cannot race the seal (queue or reject turns before cleanup starts).
- Re-create the runtime/child set if work must continue after a teardown; the sealed set cannot be reopened.
- Audit error/retry paths that may re-submit turns after the runtime's close() was invoked.
Example fix
// before
await runtime.close();
await port.startTurn({ text: "one more" }); // throws: admission closed
// after
if (runtime.isClosed()) {
throw new Error("runtime already torn down; spawn a new session");
}
await port.startTurn({ text: "one more" }); Defensive patterns
Strategy: try-catch
Validate before calling
if (runtime.isClosed || runtime.isSealed) {
throw new Error("runtime teardown already started; create a new session before starting turns");
} Try / catch
try {
const finishAdmission = children.beginLifetimeOwnershipAdmission();
// ... spawn/turn work ...
await finishAdmission();
} catch (e) {
if (e.message === "ACPX provider ownership admission is closed" ||
e.message === "ACPX provider ownership admission is already active") {
throw new Error("cannot start turns during/after runtime cleanup");
}
throw e;
} Prevention
- Serialize startTurn calls against runtime.close()/cleanup with a mutex or state flag.
- Reject new turns as soon as shutdown begins instead of letting them race the seal.
- Never start turns on a runtime after observing any cleanup-sealed error; respawn instead.
- In retries, always obtain a fresh runtime/session rather than reusing a torn-down one.
When it happens
Trigger: startTurn() invokes children.beginLifetimeOwnershipAdmission() after the child set was sealed for cleanup — i.e. a turn is started on a runtime whose cleanup/seal already ran.
Common situations: A queued or retried turn racing with runtime shutdown/cleanup; calling startTurn after the runtime was closed or the runner was torn down; a double-shutdown path sealing the set while work is still being submitted.
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
- ACPX provider ownership admission is already active
- ACPX runtime host is closing
- ACPX runtime host already has an active turn
- capability_live_attempt_active_turn
- Capability live turn admission was abandoned during teardown
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/3c9147512ca137ec.
Report an issue: GitHub.