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

  1. Do not call startTurn after runtime cleanup has begun; check the runtime's closed/sealed state before submitting turns.
  2. Serialize turn submission against shutdown so startTurn cannot race the seal (queue or reject turns before cleanup starts).
  3. Re-create the runtime/child set if work must continue after a teardown; the sealed set cannot be reopened.
  4. 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

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


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