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

  1. Ensure no package manager or build watcher mutates the package tree while snapshotting; quiesce installs first
  2. Re-run the snapshot — the error is usually a transient race, not a corrupted package
  3. Verify the file is locally readable and fully written (check on-disk size vs stat size; avoid snapshotting network mounts)
  4. 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

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


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