paperclipai/paperclip · error · Error
ACPX file ended during snapshot
Error message
ACPX file ended during snapshot
What it means
readSnapshotBytes fills a pre-allocated Buffer of exactly byteLength bytes from an open file handle for an ACPX package snapshot. It loops until the buffer is full; if a read returns 0 bytes (EOF reached before the expected length), it throws. The library treats a truncated file as a snapshot integrity failure because the stat-reported size no longer matches the file's readable content.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/private-snapshot.ts:46
digests: Record<string, string>;
handoff: { path: string; digest: string };
close(): Promise<void>;
}
const digest = (bytes: Buffer) =>
createHash("sha256").update(bytes).digest("hex");
const within = (root: string, file: string) => {
const rel = relative(root, file);
return (
rel === "" || (rel !== ".." && !rel.startsWith("../") && !isAbsolute(rel))
);
};
async function readSnapshotBytes(handle: FileHandle, byteLength: number): Promise<Buffer> {
const bytes = Buffer.alloc(byteLength);
let offset = 0;
while (offset < bytes.length) {
const read = await handle.read(bytes, offset, bytes.length - offset, offset);
if (!read.bytesRead) throw new Error("ACPX file ended during snapshot");
offset += read.bytesRead;
}
return bytes;
}
/** macOS has no /proc directory descriptors. Freeze only the admitted package roots. */
export async function createAcpxPrivateSnapshot(
sourceRoots: readonly string[],
executable: FileHandle | null,
): Promise<AcpxPrivateSnapshot> {
sourceRoots = await Promise.all(sourceRoots.map((root) => realpath(root)));
const sourceIdentities = await Promise.all(
sourceRoots.map((root) => lstat(root, { bigint: true })),
);
const directory = await realpath(
await mkdtemp(join(tmpdir(), "paperclip-acpx-")),
);
const roots = sourceRoots.map((_, index) => join(directory, String(index)));View on GitHub (pinned to 01ad858492)
Solutions
- Ensure no package manager or build watcher mutates the package tree while snapshotting; quiesce installs first
- Re-run the snapshot — the error is usually a transient race, not a corrupted package
- Verify the file is locally readable and fully written (check on-disk size vs stat size; avoid snapshotting network mounts)
- Pin/lock dependencies so package directories are not replaced mid-run
Example fix
// before: snapshot taken while install runs
await exec('npm install &');
await snapshot(roots);
// after: quiesce installs before snapshot
await exec('npm install');
await snapshot(roots); Defensive patterns
Strategy: retry
Validate before calling
const st = await fs.stat(file);
if (!st.isFile()) throw new Error('not a regular file');
// readable size sanity check
const fd = await fs.open(file, 'r');
const buf = Buffer.alloc(1);
await fd.read(buf, 0, 1, Math.max(0, st.size - 1)); Try / catch
try {
await snapshot(roots);
} catch (e) {
if (e.message.includes('ended during snapshot')) {
await waitForTreeIdle(roots);
await snapshot(roots); // retry once after quiescing writers
} else throw e;
} Prevention
- Never run package installs concurrently with session snapshots
- Exclude log/cache directories from admitted package roots
- Avoid snapshotting files on network mounts where stat size can exceed readable bytes
When it happens
Trigger: Calling createAcpxPrivateSnapshot (via privateSnapshot/snapshot) on a package file whose size shrinks or is truncated between the lstat/handle.stat size check and the sequential reads inside readSnapshotBytes — i.e. read.bytesRead becomes 0 at some offset.
Common situations: A concurrent process rewrites/truncates a node_modules file during snapshot (e.g. pnpm/npm install running at the same time); a sparse or network-mounted file whose stat size exceeds readable bytes; a stale build artifact partially deleted by a watcher/cleaner.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- ACPX package directory changed during snapshot
- ACPX module changed before snapshot
- ACPX module changed during snapshot
- ACPX package root changed during snapshot
- ACPX ${agent} runtime executable changed while it was verifi
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/4559bcd8b8cd93e6.
Report an issue: GitHub.