paperclipai/paperclip · error · Error
ACPX ${agent} runtime executable must be a real regular file
Error message
ACPX ${agent} runtime executable must be a real regular file What it means
`openVerifiedRuntimeExecutable` in packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts performs a TOCTOU-hardened verification of the ACPX runtime executable before executing it. Before opening the file, it `lstat`s the path and requires the entry to exist and be a real regular file (not a symlink, directory, fifo, etc.). If the lstat fails or the entry is anything other than a regular file, this error is thrown to refuse running a potentially attacker-controlled path.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:1026
} finally {
await handle.close();
}
}
async function openVerifiedRuntimeExecutable(
executablePath: string,
expectedDigest: string,
agent: string,
): Promise<{ handle: FileHandle; identity: VerifiedAcpxCommandIdentity }> {
const lexicalBefore = await lstat(executablePath, { bigint: true }).catch(
() => null,
);
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 });View on GitHub (pinned to 01ad858492)
Solutions
- Verify the configured executable path exists and is a regular file, not a symlink: `lstat` it yourself before configuring the runtime
- Replace symlinked bin paths with the resolved real binary path (`fs.realpathSync` then check `statSync(...).isFile()` and reject if still a symlink at lstat level)
- Reinstall/repair the ACPX runtime so the binary is materialized on disk at the expected path
- Check parent-directory permissions so the process can stat the path
- Point the runtime at the canonical install location managed by the installer rather than a user-provided PATH lookup
Example fix
// before
const bin = '/usr/local/bin/acpx'; // symlink into node_modules
await spawnAcppRuntime({ executablePath: bin });
// after
import { lstatSync } from 'node:fs';
const st = lstatSync('/usr/local/bin/acpx');
if (st.isSymbolicLink() || !st.isFile()) {
throw new Error('configure the resolved real binary path, not a symlink');
}
await spawnAcppRuntime({ executablePath: '/usr/local/lib/acpx/bin/acpx' }); Defensive patterns
Strategy: validation
Validate before calling
import { lstatSync } from 'node:fs';
function isRealRegularFile(p: string): boolean {
try {
const st = lstatSync(p);
return !st.isSymbolicLink() && st.isFile();
} catch {
return false;
}
}
if (!isRealRegularFile(executablePath)) {
throw new Error(`resolve acpx binary to a real file: ${executablePath}`);
} Type guard
function isRealRegularStat(st: { isFile(): boolean; isSymbolicLink(): boolean }): boolean {
return !st.isSymbolicLink() && st.isFile();
} Prevention
- Always configure the realpath of the binary, never a symlinked PATH entry or .bin shim
- Pre-flight check the executable path with lstat before starting runs
- Reinstall the runtime if the path is missing instead of hand-placing files
- Keep the install directory out of paths managed by aggressive cleanup/AV tools
When it happens
Trigger: Calling openVerifiedRuntimeExecutable (via the ACPX runtime spawn path) when `lstat(executablePath)` returns null (path missing/unreadable), the entry is a symlink (common with npm-installed bins in node_modules/.bin), or the entry is a directory/device/fifo rather than a regular file.
Common situations: ACPX binary path misconfigured (points at a wrapper script dir or a symlinked global install); npm/pnpm 'bin' shims that are symlinks; binary not downloaded yet so the path does not exist; permissions on a parent directory deny stat; antivirus or cleanup tool removed the file mid-session.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Trusted viewer must not use symlinks
- ACPX provider package manifest resolves outside the selected
- ACPX provider node_modules resolves outside the selected pro
- ACPX provider package issuer for ${packageName} resolves out
- ACPX ${agent} runtime executable could not be opened as a no
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/54e79e76d5f06635.
Report an issue: GitHub.