{"record":{"id":"844fcb4e657fb7cf","repo":"paperclipai/paperclip","slug":"acpx-agent-runtime-executable-could-not-be-open","errorCode":null,"errorMessage":"ACPX ${agent} runtime executable could not be opened as a no-follow regular file","messagePattern":"ACPX (.+?) runtime executable could not be opened as a no-follow regular file","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":1038,"sourceCode":"  );\n  if (\n    lexicalBefore === null ||\n    lexicalBefore.isSymbolicLink() ||\n    !lexicalBefore.isFile()\n  ) {\n    throw new Error(\n      `ACPX ${agent} runtime executable must be a real regular file`,\n    );\n  }\n\n  let handle: FileHandle;\n  try {\n    handle = await open(\n      executablePath,\n      verifiedExecutableOpenFlags(process.platform, constants.O_NOFOLLOW),\n    );\n  } catch {\n    throw new Error(\n      `ACPX ${agent} runtime executable could not be opened as a no-follow regular file`,\n    );\n  }\n\n  try {\n    const before = await handle.stat({ bigint: true });\n    if (\n      !before.isFile() ||\n      before.size < 1n ||\n      before.size > BigInt(MAX_ACPX_RUNTIME_EXECUTABLE_BYTES) ||\n      (before.mode & 0o111n) === 0n\n    ) {\n      throw new Error(\n        `ACPX ${agent} runtime executable must be a bounded executable file`,\n      );\n    }\n    const hash = createHash(\"sha256\");\n    const buffer = Buffer.alloc(1024 * 1024);","sourceCodeStart":1020,"sourceCodeEnd":1056,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L1020-L1056","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Re-run the runtime start after the concurrent install/upgrade finishes — the file was likely being swapped","Check file ownership/permissions: the service user needs read (and open) access to the binary and its parent dirs","Confirm no security module (SELinux/AppArmor) is denying opens of the runtime path","Ensure the binary lives on a local filesystem supporting O_NOFOLLOW semantics, not an exotic mount","Reinstall the ACPX runtime atomically so no partial/symlinked state exists"],"exampleFix":"// before: binary replaced by a symlink mid-upgrade\nls -l /usr/local/bin/acpx  # acpx -> /opt/acpx-next/bin/acpx  (ELOOP on open)\n// after: wait for upgrade to complete and verify\nlstat /opt/acpx/bin/acpx   # regular file, not symlink\n/opt/acpx/bin/acpx --version","handlingStrategy":"retry","validationCode":"import { accessSync, constants as fsConstants } from 'node:fs';\nfunction pathReadable(p: string): boolean {\n  try { accessSync(p, fsConstants.R_OK); return true; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await startAcppRuntime();\n} catch (e) {\n  if (String(e?.message).includes('could not be opened as a no-follow regular file')) {\n    await waitForUpgradeToFinish(); // concurrent install/swap likely\n    await startAcppRuntime(); // retry once\n  } else {\n    throw e;\n  }\n}","preventionTips":["Avoid starting runs while an install/upgrade of the runtime is in progress","Grant the service user read access to the binary and parent directories","Check SELinux/AppArmor audit logs if opens are denied on hardened hosts","Use atomic installs (temp file + rename) so O_NOFOLLOW never trips on a swap"],"tags":["filesystem","security","acpx","toctou"],"backgroundTag":"file-open-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}