paperclipai/paperclip · error

ACPX provider dependency ancestry is invalid

Error message

ACPX provider dependency ancestry is invalid

What it means

This error is thrown inside the generated provider-lifetime watchdog script (a template-literal-generated Node script in installation-integrity.ts). Before spawning, the watchdog parses process.argv[4] as dependencyAncestorCount and validates it is a safe, non-negative integer not exceeding MAX_DEPENDENCY_ANCESTORS. An invalid value means the file-descriptor layout (PROVIDER_RUNTIME_EXECUTABLE_FD, OWNER_FD, etc.) cannot be computed safely, so the watchdog aborts.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:141

const owner = fs.createReadStream("", { fd: 3, autoClose: false });
owner.once("end", reap);
owner.once("error", reap);
owner.resume();
try {
  fs.writeSync(4, "armed\\n");
} catch {
  reap();
}
`;

export const PROVIDER_LIFETIME_GUARDIAN_SOURCE = `
const fs = require("node:fs");
const { spawn } = require("node:child_process");
const WATCHDOG_SOURCE = ${JSON.stringify(PROVIDER_LIFETIME_WATCHDOG_SOURCE)};
const runtimeExecutable = process.env.${VERIFIED_RUNTIME_EXECUTABLE_ENV} || process.execPath;
const dependencyAncestorCount = Number.parseInt(process.argv[4], 10);
const providerRuntimeExecutableCount = Number.parseInt(process.argv[8], 10);
if (!Number.isSafeInteger(dependencyAncestorCount) || dependencyAncestorCount < 0 || dependencyAncestorCount > ${MAX_DEPENDENCY_ANCESTORS}) throw new Error("ACPX provider dependency ancestry is invalid");
if (providerRuntimeExecutableCount !== 0 && providerRuntimeExecutableCount !== 1) throw new Error("ACPX provider runtime executable count is invalid");
const PROVIDER_RUNTIME_EXECUTABLE_FD = ${DEPENDENCY_ANCESTOR_FD_START} + dependencyAncestorCount;
const OWNER_FD = PROVIDER_RUNTIME_EXECUTABLE_FD + providerRuntimeExecutableCount;
const OWNERSHIP_FD = OWNER_FD + 1;
const PROVIDER_EXIT_FD = OWNERSHIP_FD + 1;
const CREDENTIAL_FENCE_FD_START = PROVIDER_EXIT_FD + 1;
const VERIFIED_RUNTIME_FD = CREDENTIAL_FENCE_FD_START + 2;
const dependencyAncestorFds = Array.from({ length: dependencyAncestorCount }, (_, index) => ${DEPENDENCY_ANCESTOR_FD_START} + index);
const runtimeDescriptorMatch = /^\\/proc\\/self\\/fd\\/([0-9]+)$/.exec(runtimeExecutable);
const runtimeDescriptorFd = runtimeDescriptorMatch === null ? null : Number.parseInt(runtimeDescriptorMatch[1], 10);
if (runtimeDescriptorFd !== null && runtimeDescriptorFd !== VERIFIED_RUNTIME_FD) throw new Error("ACPX verified runtime descriptor is misplaced");
if (runtimeDescriptorFd !== null) fs.fstatSync(runtimeDescriptorFd);
let provider;
let watchdog;
let reaped = false;
let shutdownStarted = false;
const reap = () => {
  if (reaped) return;

View on GitHub (pinned to 01ad858492)

Solutions

  1. Inspect the spawn call that launches the watchdog and confirm argv[4] receives the dependency-ancestor count as a decimal string within [0, MAX_DEPENDENCY_ANCESTORS].
  2. If the real dependency ancestry exceeds MAX_DEPENDENCY_ANCESTORS, raise the constant (and DEPENDENCY_ANCESTOR_FD_START layout accordingly) or trim the ancestor set.
  3. Guard the parent side: Number.isSafeInteger(count) && count >= 0 && count <= MAX_DEPENDENCY_ANCESTORS before spawning.
  4. Verify no recent refactor shifted the argv positions consumed by the generated script.

Example fix

// before: count possibly undefined/NaN
spawn(process.execPath, [watchdogEntry, ..., String(ancestors?.length), ...]);

// after: validate before spawn
const count = ancestors?.length ?? 0;
if (!Number.isSafeInteger(count) || count < 0 || count > MAX_DEPENDENCY_ANCESTORS) {
  throw new Error(`dependency ancestor count out of range: ${count}`);
}
spawn(process.execPath, [watchdogEntry, ..., String(count), ...]);
Defensive patterns

Strategy: validation

Validate before calling

const count = ancestors?.length ?? 0;
if (!Number.isSafeInteger(count) || count < 0 || count > MAX_DEPENDENCY_ANCESTORS) {
  throw new Error(`dependency ancestor count out of range: ${count}`);
}
// then pass String(count) at the argv position the watchdog expects

Try / catch

try {
  await spawnProviderWatchdog(args);
} catch (err) {
  if (err.message.includes('dependency ancestry is invalid')) {
    // recompute/trim ancestry or raise MAX_DEPENDENCY_ANCESTORS, then retry once
    args[4] = String(Math.min(ancestors.length, MAX_DEPENDENCY_ANCESTORS));
    await spawnProviderWatchdog(args);
  } else throw err;
}

Prevention

When it happens

Trigger: The parent (installation-integrity code) passes a malformed or out-of-range argv[4] to the watchdog child process: non-numeric string, NaN, negative number, or a count greater than MAX_DEPENDENCY_ANCESTORS. This can happen if the computed dependency ancestor list grew beyond the cap or a variable interpolation in the spawning code passed the wrong argument position.

Common situations: A deeply nested dependency tree exceeding MAX_DEPENDENCY_ANCESTORS after an install changed; refactoring the spawn argv ordering so argv[4] now receives a different value; an environment where the ancestors array is empty/malformed and String(...) yields 'undefined', which parseInt turns into NaN.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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