paperclipai/paperclip · critical

Managed Codex credential ownership was lost

Error message

Managed Codex credential ownership was lost

What it means

Managed Codex credentials are guarded by a lease quorum of listening lock servers plus validity checks. assertHeld throws when ownership is no longer provable: the lease was released, a lock server reported the lease invalid, or fewer servers than CREDENTIAL_LEASE_QUORUM report listening. Called by inheritanceFds and activateLifetimeOwner, this means credential inheritance or lifetime-owner activation cannot proceed safely because another process may now own the credential.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/codex-credentials.ts:436

    inheritanceFds = Object.freeze([first, second]) as readonly [
      number,
      number,
    ];
  } catch (error) {
    released = true;
    await Promise.allSettled(servers.map(closeCredentialLeaseServer));
    throw error;
  }

  return Object.freeze({
    assertHeld(): void {
      if (
        released ||
        invalid !== null ||
        servers.filter((server) => server.listening).length <
          CREDENTIAL_LEASE_QUORUM
      ) {
        throw new Error("Managed Codex credential ownership was lost");
      }
    },
    candidatePorts(): readonly [number, number, number] {
      return candidatePorts;
    },
    inheritanceFds(): readonly [number, number] {
      this.assertHeld();
      return inheritanceFds;
    },
    async activateLifetimeOwner(pid: number): Promise<void> {
      this.assertHeld();
      if (!Number.isSafeInteger(pid) || pid < 1) {
        throw new Error("Managed Codex credential lifetime owner is invalid");
      }
    },
    async release(): Promise<void> {
      if (released) return;
      const outcomes = await Promise.allSettled(

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-acquire the managed credential lease (re-run the credential acquisition/lock flow) before retrying inheritanceFds or activateLifetimeOwner.
  2. Check that all quorum lock server ports are listening; restart the lease servers and resolve any port conflicts (use candidatePorts() to pick free ports).
  3. Ensure only one runner instance owns the credential at a time; stop the competing process.
  4. Retry promptly after re-acquisition and keep the holder process alive through fork/inheritance to avoid releasing mid-operation.

Example fix

// before
const fds = lease.inheritanceFds(); // lease already released -> throws
// after
if (!lease.isHeld()) await reAcquireCredentialLease(lease.candidatePorts());
const fds = lease.inheritanceFds();
Defensive patterns

Strategy: retry

Validate before calling

function leaseIsUsable(lease) {
  return !lease.released && lease.invalid === null &&
    lease.servers.filter((s) => s.listening).length >= CREDENTIAL_LEASE_QUORUM;
}
if (!leaseIsUsable(lease)) await reAcquireLease();

Type guard

function holdsQuorum(lease) {
  return lease.servers.filter((s) => s.listening).length >= CREDENTIAL_LEASE_QUORUM;
}

Try / catch

try {
  const fds = lease.inheritanceFds();
} catch (err) {
  if (err.message === "Managed Codex credential ownership was lost") {
    await reAcquireCredentialLease(lease.candidatePorts());
    // then retry inheritance once
  } else throw err;
}

Prevention

When it happens

Trigger: Calling inheritanceFds() or activateLifetimeOwner() after the lease was released; a quorum lock server stopped listening; the lease was invalidated by another claimant winning the lifetime-owner election.

Common situations: Runner process restarts or fork timing causing the lease to lapse; port conflicts preventing quorum servers from listening; two runner instances contending for the same managed credential; machine sleep/resume expiring the lease.

Related errors


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