paperclipai/paperclip · error · AggregateError

ACPX command leases did not close

Error message

ACPX command leases did not close

What it means

Aggregate close failure in the verified ACPX command-lease owner: close() attempts to close every acquired lease, collects per-lease failures, and throws this aggregated error when any lease close failed (late acquisitions after closing starts remain owned and retried, but ones that still fail are surfaced). It signals that not all underlying command leases were released cleanly and some resource handle may still be open.

Solutions

  1. Read the AggregateError's inner failures to identify which lease(s) failed to close.
  2. Retry close after the underlying command/transport settles — the owner already retries each failing lease once during close.
  3. Investigate why a lease's close hung or errored (spawned process still running, transport stuck) and ensure leases are only acquired while the owner is open.
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at packages/paperclip-runner/src/drivers/acpx/command-lease-owner.ts:32 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/758f31a2cbd5d932. Report an issue: GitHub.

Appendix: source

Thrown at packages/paperclip-runner/src/drivers/acpx/command-lease-owner.ts:32

    spawn(...args) {
      if (closing) throw new Error("Verified ACPX command owner is closing");
      consumed = true;
      return current.spawn(...args);
    },
    async close() {
      closing = true;
      // Late acquisitions remain owned. Retry every lease whose close fails.
      await refresh?.catch(() => undefined);
      const failures: unknown[] = [];
      for (const lease of leases) {
        try {
          await lease.close();
          leases.delete(lease);
        } catch (error) {
          failures.push(error);
        }
      }
      if (failures.length) throw new AggregateError(failures, "ACPX command leases did not close");
    },
  };
  return {
    command,
    async refreshConsumedCommand(): Promise<void> {
      if (closing) throw new Error("Verified ACPX command owner is closing");
      if (!consumed) return;
      if (!refresh) {
        refresh = Promise.resolve()
          .then(openCommand)
          .then((replacement) => {
            leases.add(replacement);
            if (closing) throw new Error("Verified ACPX command owner closed during refresh");
            current = replacement;
            consumed = false;
          });
      }
      const pending = refresh;

View on GitHub (pinned to 3f1d897a7c)