paperclipai/paperclip · critical
ACPX verified runtime descriptor is misplaced
Error message
ACPX verified runtime descriptor is misplaced
What it means
When spawning the ACPX verified runtime, the code expects the runtime executable to arrive as the /proc/self/fd path pointing exactly at the reserved VERIFIED_RUNTIME_FD descriptor (CREDENTIAL_FENCE_FD_START + 2). If the parsed fd number differs from the reserved one, the fd wiring between caller and spawner is broken, so the integrity check fails closed rather than spawning an unverified binary. This protects against the runtime being executed from an unexpected or unvetted descriptor.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:152
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;
reaped = true;
// This sentinel is the provider group's leader. It remains alive until this
// one atomic signal, pinning the numeric group identity against PID reuse.
process.kill(-process.pid, "SIGKILL");
};
const owner = fs.createReadStream("", { fd: OWNER_FD, autoClose: false });
owner.once("end", reap);
owner.once("error", reap);
owner.resume();
// Fail before provider code exists unless both inherited quorum fences are live.
fs.fstatSync(CREDENTIAL_FENCE_FD_START);View on GitHub (pinned to 01ad858492)
Solutions
- Compare the reserved fd constants (OWNERSHIP_FD, PROVIDER_EXIT_FD, CREDENTIAL_FENCE_FD_START, VERIFIED_RUNTIME_FD) against the fd number actually passed as runtimeExecutable; re-align the caller so the verified runtime is dup'd onto VERIFIED_RUNTIME_FD
- Check dependencyAncestorCount: any change to the number of ancestor fds shifts the fence layout; update CREDENTIAL_FENCE_FD_START derivation accordingly
- Log /proc/self/fd listings at spawn time to see which fd the runtime actually arrived on and fix the dup2/fd inheritance in the caller
- Remove duplicate or stale fd holders (e.g. leftover watchdogs) that occupy the reserved fd before the runtime is passed
Example fix
// before
const fd = parseFd(runtimeExecutable); // e.g. 5, but VERIFIED_RUNTIME_FD is 6
spawn(fd, opts);
// after
import { VERIFIED_RUNTIME_FD } from './installation-integrity';
dup2(runtimeFd, VERIFIED_RUNTIME_FD);
spawn(`/proc/self/fd/${VERIFIED_RUNTIME_FD}`, opts); Defensive patterns
Strategy: validation
Validate before calling
import { VERIFIED_RUNTIME_FD } from './installation-integrity';
import * as fs from 'node:fs';
const m = /^\/proc\/self\/fd\/([0-9]+)$/.exec(runtimeExecutable);
if (m && Number(m[1]) !== VERIFIED_RUNTIME_FD) {
throw new Error(`runtime fd ${m[1]} != reserved VERIFIED_RUNTIME_FD ${VERIFIED_RUNTIME_FD}`);
} Type guard
function isVerifiedRuntimeFd(executable: string): boolean {
const m = /^\/proc\/self\/fd\/([0-9]+)$/.exec(executable);
return m === null || Number(m[1]) === VERIFIED_RUNTIME_FD;
} Try / catch
try {
spawnAcpxRuntime(runtimeExecutable, opts);
} catch (err) {
if (err instanceof Error && err.message === 'ACPX verified runtime descriptor is misplaced') {
logger.error({ fds: fs.readdirSync('/proc/self/fd') }, 'fd fence misaligned');
throw new Error('ACPX fd wiring broken: re-align VERIFIED_RUNTIME_FD in the caller');
}
throw err;
} Prevention
- Centralize the fd constants (OWNERSHIP_FD, CREDENTIAL_FENCE_FD_START, VERIFIED_RUNTIME_FD) in one exported module and derive all users from it
- Add a unit test that spawns with a deliberately wrong fd and asserts the guard fires
- Log the full /proc/self/fd listing before spawn in debug mode to catch fence shifts early
- Never dup2 or open new files into the reserved fd range in unrelated code paths
When it happens
Trigger: Calling the spawn path with a runtimeExecutable that matches /proc/self/fd/<n> where n is not VERIFIED_RUNTIME_FD — i.e. the fd was passed on a different descriptor number than the reserved slot.
Common situations: fd reservation constants (OWNERSHIP_FD, PROVIDER_EXIT_FD, CREDENTIAL_FENCE_FD_START) changed in one place but the spawning code or dependency-ancestor fd count was not updated, so the verified runtime lands on a shifted fd; double-spawning two children that both consume fence fds; an intermediate wrapper re-executing and renumbering fds.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- ACPX provider dependency ancestry is invalid
- ACPX provider runtime executable count is invalid
- The setup-token login session could not start.
- Native ACPX backend for pi is unavailable until descriptor-c
- The Pi ACPX profile is not available
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/d5ad9eace5a6427a.
Report an issue: GitHub.