paperclipai/paperclip · error · Error

ACPX provider lifetime fence candidates are invalid

Error message

ACPX provider lifetime fence candidates are invalid

What it means

validateFenceCandidates requires the provider lifetime fence candidates to be exactly an array of 3 distinct safe integers in the ephemeral/dynamic port range 49152–65535. These ports are used to fence provider lifetime across session recovery. Anything else (wrong length, duplicates, out-of-range, non-integer) is rejected with this error so recovery cannot proceed against invalid fencing data.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts:277

    !isPermissionMode(expected.permissionMode)
  ) {
    throw new Error("Expected ACPX permission mode is invalid");
  }
  validateFenceCandidates(expected.providerLifetimeFenceCandidates);
}

function validateFenceCandidates(
  value: unknown,
): asserts value is readonly [number, number, number] {
  if (
    !Array.isArray(value) ||
    value.length !== 3 ||
    value.some(
      (port) => !Number.isSafeInteger(port) || port < 49_152 || port > 65_535,
    ) ||
    new Set(value).size !== 3
  ) {
    throw new Error("ACPX provider lifetime fence candidates are invalid");
  }
}

function sameFenceCandidates(
  left: readonly [number, number, number],
  right: readonly [number, number, number],
): boolean {
  return left.every((port, index) => port === right[index]);
}

async function resolveWorkspace(value: string): Promise<string> {
  if (!value.trim()) throw new Error("ACPX working directory is required");
  const workspacePath = await realpath(value);
  const metadata = await stat(workspacePath);
  if (!metadata.isDirectory() || workspacePath === dirname(workspacePath)) {
    throw new Error("ACPX working directory must be a non-root directory");
  }
  return workspacePath;

View on GitHub (pinned to 01ad858492)

Solutions

  1. Regenerate fence candidates so they are 3 distinct safe integers within 49152–65535 (the OS ephemeral range).
  2. Discard the corrupt persisted record and start a new session so the library can regenerate valid candidates.
  3. Fix fixtures/tests to build candidates with the library's own generator rather than hardcoding.
  4. If you persist records through your own pipeline, validate the 3-distinct-ephemeral-ports invariant before writing.

Example fix

// before
const candidates = [8080, 8080]; // reserved ports and duplicates
// after
const candidates = [49152 + 100, 49152 + 200, 49152 + 300]; // distinct, in 49152-65535
Defensive patterns

Strategy: validation

Validate before calling

const valid = (c: unknown): c is [number, number, number] =>
  Array.isArray(c) && c.length === 3 &&
  c.every((p) => Number.isSafeInteger(p) && p >= 49152 && p <= 65535) &&
  new Set(c).size === 3;

Try / catch

try {
  const record = parsePersistedRecord(raw);
} catch (e) {
  if (e.message === "ACPX provider lifetime fence candidates are invalid") {
    return startFreshSession(); // regenerate valid candidates
  }
  throw e;
}

Prevention

When it happens

Trigger: Parsing a persisted identity record (via validatedRecord) or validating an expected identity (via validateExpected) whose providerLifetimeFenceCandidates is not an array of exactly 3 distinct integers in [49152, 65535] — e.g. undefined, empty array, port 80, or repeated ports.

Common situations: Hand-crafted test fixtures with placeholder ports; records truncated by a bad serializer; code generating fence candidates from a reserved port range; JSON round-trips that lost array elements or produced floats.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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