paperclipai/paperclip · error · Error

ACPX ${agent} runtime executable could not be opened as a no

Error message

ACPX ${agent} runtime executable could not be opened as a no-follow regular file

What it means

After the lexical lstat check passes, the runtime opens the executable with `O_NOFOLLOW` (via verifiedExecutableOpenFlags) so the kernel refuses to traverse a symlink swapped in between the lstat and the open (TOCTOU defense). If `open` throws for any reason — O_NOFOLLOW rejection, EACCES, ENOENT from a swap, etc. — the library cannot guarantee a safe handle and throws this error instead of surfacing the raw errno.

Source

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

  );
  if (
    lexicalBefore === null ||
    lexicalBefore.isSymbolicLink() ||
    !lexicalBefore.isFile()
  ) {
    throw new Error(
      `ACPX ${agent} runtime executable must be a real regular file`,
    );
  }

  let handle: FileHandle;
  try {
    handle = await open(
      executablePath,
      verifiedExecutableOpenFlags(process.platform, constants.O_NOFOLLOW),
    );
  } catch {
    throw new Error(
      `ACPX ${agent} runtime executable could not be opened as a no-follow regular file`,
    );
  }

  try {
    const before = await handle.stat({ bigint: true });
    if (
      !before.isFile() ||
      before.size < 1n ||
      before.size > BigInt(MAX_ACPX_RUNTIME_EXECUTABLE_BYTES) ||
      (before.mode & 0o111n) === 0n
    ) {
      throw new Error(
        `ACPX ${agent} runtime executable must be a bounded executable file`,
      );
    }
    const hash = createHash("sha256");
    const buffer = Buffer.alloc(1024 * 1024);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-run the runtime start after the concurrent install/upgrade finishes — the file was likely being swapped
  2. Check file ownership/permissions: the service user needs read (and open) access to the binary and its parent dirs
  3. Confirm no security module (SELinux/AppArmor) is denying opens of the runtime path
  4. Ensure the binary lives on a local filesystem supporting O_NOFOLLOW semantics, not an exotic mount
  5. Reinstall the ACPX runtime atomically so no partial/symlinked state exists

Example fix

// before: binary replaced by a symlink mid-upgrade
ls -l /usr/local/bin/acpx  # acpx -> /opt/acpx-next/bin/acpx  (ELOOP on open)
// after: wait for upgrade to complete and verify
lstat /opt/acpx/bin/acpx   # regular file, not symlink
/opt/acpx/bin/acpx --version
Defensive patterns

Strategy: retry

Validate before calling

import { accessSync, constants as fsConstants } from 'node:fs';
function pathReadable(p: string): boolean {
  try { accessSync(p, fsConstants.R_OK); return true; } catch { return false; }
}

Try / catch

try {
  await startAcppRuntime();
} catch (e) {
  if (String(e?.message).includes('could not be opened as a no-follow regular file')) {
    await waitForUpgradeToFinish(); // concurrent install/swap likely
    await startAcppRuntime(); // retry once
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: `fs.open(executablePath, verifiedExecutableOpenFlags(process.platform, O_NOFOLLOW))` rejects: the path was replaced by a symlink after lstat (ELOOP), permissions changed (EACCES), the file was deleted between lstat and open (ENOENT), or the platform lacks a required open flag.

Common situations: Concurrent install/upgrade process swaps the binary for a symlink while a run is starting; restrictive umask or ownership makes the file unopenable by the service user; hardened environments (SELinux/AppArmor) deny open; NFS/network filesystems where symlink semantics or open flags behave unexpectedly.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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