paperclipai/paperclip · error

providerLifetimeFenceCandidates must be three distinct priva

Error message

providerLifetimeFenceCandidates must be three distinct private ports

What it means

providerLifetimeFenceCandidates must be exactly three distinct TCP ports in the private/dynamic range (49152-65535); the validator freezes the tuple only when the shape, range, and uniqueness checks all pass. This prevents fence ports from colliding or overlapping with privileged ranges.

Source

Thrown at packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts:1111

      : { permissionMode: requiredPermissionMode(input.permissionMode) }),
    providerLifetimeFenceCandidates: requiredFenceCandidates(
      input.providerLifetimeFenceCandidates,
    ),
  };
}

function requiredFenceCandidates(
  value: unknown,
): 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(
      "providerLifetimeFenceCandidates must be three distinct private ports",
    );
  }
  return Object.freeze([...value]) as readonly [number, number, number];
}

function requiredPermissionMode(
  value: unknown,
): AcpxSidecarOpenParams["permissionMode"] {
  if (
    value === "approve-all" ||
    value === "approve-reads" ||
    value === "deny-all"
  ) {
    return value;
  }
  throw new Error(
    "permissionMode must be approve-all, approve-reads, or deny-all",

View on GitHub (pinned to 01ad858492)

Solutions

  1. Provide exactly three different ports, each between 49152 and 65535
  2. Fix the config source so duplicates are resolved before validation
  3. Generate three distinct ephemeral ports programmatically

Example fix

// before
normalizeFenceCandidates([49200, 49200, 49300]);
// after
normalizeFenceCandidates([49200, 49201, 49300]);
Defensive patterns

Strategy: validation

Validate before calling

function validFencePorts(ports) { return Array.isArray(ports) && ports.length === 3 && ports.every(p => Number.isSafeInteger(p) && p >= 49152 && p <= 65535) && new Set(ports).size === 3; }
if (!validFencePorts(config.providerLifetimeFenceCandidates)) throw new Error('need three distinct ports in 49152-65535');

Type guard

function isFenceCandidateTuple(v: unknown): v is readonly [number, number, number] {
  return Array.isArray(v) && v.length === 3 && v.every((p): p is number => Number.isSafeInteger(p) && p >= 49152 && p <= 65535) && new Set(v).size === 3;
}

Try / catch

try { configureFences(cfg.providerLifetimeFenceCandidates); }
catch (e) { if (String(e.message).includes('three distinct private ports')) { cfg.providerLifetimeFenceCandidates = pickDistinctEphemeralPorts(3); configureFences(cfg.providerLifetimeFenceCandidates); } else throw e; }

Prevention

When it happens

Trigger: Passing an array whose length is not 3, containing non-integer or out-of-range ports (<49152 or >65535), or containing duplicate port values.

Common situations: Config with two or three identical ports from copy-paste; ports picked from the well-known/registered range; a config loader returning a 2-element list after dropping a duplicate.

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/1ed6fce29444d339. Report an issue: GitHub.