paperclipai/paperclip · error

ACPX package directory changed while snapshotting

Error message

ACPX package directory changed while snapshotting

What it means

On macOS, openCommand creates a private snapshot of the command directory and dependency ancestors, then re-checks each directory via lstat against the identity of the still-open directory handle before using the snapshot. If any path is now a symlink or its device/inode identity differs from the held handle, the tree changed mid-snapshot and the snapshot is rejected to avoid copying partially replaced content.

Source

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

        if (!sameIdentity(current.identity, commandIdentity)) {
          current.bytes.fill(0);
          throw new Error(
            "ACPX provider executable identity changed after verification",
          );
        }
        const privateSnapshot = process.platform === "darwin"
          ? await createAcpxPrivateSnapshot([commandDirectory, ...dependencyAncestors.map((root) => root.path)], currentRuntimeExecutable)
          : null;
        if (privateSnapshot) {
          try {
            // Bind copied trees to the identities retained by the verified lease.
            const paths = [commandDirectory, ...dependencyAncestors.map((root) => root.path)];
            const handles = [currentDirectory.handle, ...currentDependencyAncestors];
            for (let index = 0; index < paths.length; index++) {
              const lexical = await lstat(paths[index]!, { bigint: true });
              const held = await handles[index]!.stat({ bigint: true });
              if (lexical.isSymbolicLink() || !sameIdentity(fileIdentity(lexical), fileIdentity(held))) {
                throw new Error("ACPX package directory changed while snapshotting");
              }
            }
            if (runtimeExecutable && privateSnapshot.executable &&
              `sha256:${privateSnapshot.digests[privateSnapshot.executable]}` !== runtimeExecutable.digest) {
              throw new Error("ACPX runtime snapshot digest mismatch");
            }
          } catch (error) { await privateSnapshot.close(); throw error; }
        }
        return commandLease(
          commandDirectory,
          basename(commandPath),
          commandFormat,
          current.bytes,
          currentDirectory.handle,
          currentDependencyAncestors,
          serverDependencyAncestorCount,
          serverPackageFormat,
          dependencyAncestorFormats,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry: re-run the installation/verification step and call openCommand again once the filesystem is quiet.
  2. Quiesce installers/sync tools so nothing mutates node_modules during agent launch.
  3. Exclude the install tree from iCloud Drive/Dropbox and other path-replacing services.
  4. Investigate which path changed (enable logging around the loop) — a symlink result usually means a package manager re-linked the directory.

Example fix

// before
const lease = await verified.openCommand(); // pnpm relink raced the snapshot
// after
await child(helper("pnpm install --frozen-lockfile")); // finish relinking first
const lease = await reverified.openCommand();
Defensive patterns

Strategy: retry

Validate before calling

const st = await fs.promises.lstat(commandDirectory, { bigint: true });
if (st.isSymbolicLink()) throw new Error("command directory is a symlink; fix install layout before opening");

Try / catch

try {
  lease = await verified.openCommand();
} catch (e) {
  if (e.message === "ACPX package directory changed while snapshotting") {
    await quiesceInstallers();
    verified = await verifyQualifiedAcpxInstallation(input);
    lease = await verified.openCommand();
  } else throw e;
}

Prevention

When it happens

Trigger: openCommand on darwin inside the privateSnapshot block, where for any of commandDirectory or the dependency-ancestor paths, lstat returns isSymbolicLink() === true or fileIdentity(lexical) differs from fileIdentity(held) from the open directory handle — a concurrent replacement/rename occurred while createAcpxPrivateSnapshot ran.

Common situations: npm/pnpm install re-linking directories during launch; a sync client (iCloud/Dropbox) materializing or replacing directories; another process deleting and recreating the node_modules subtree; test suites that reinstall fixtures between verify and open.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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