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
- Retry: re-run the installation/verification step and call openCommand again once the filesystem is quiet.
- Quiesce installers/sync tools so nothing mutates node_modules during agent launch.
- Exclude the install tree from iCloud Drive/Dropbox and other path-replacing services.
- 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
- Keep the install tree out of iCloud Drive/Dropbox and any path-replacing sync service.
- Quiesce installers and CI jobs that touch node_modules during agent launch.
- Expect this on macOS only; test snapshot behavior after any change to install tooling.
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
- ACPX runtime snapshot digest mismatch
- ACPX provider runtime executable identity changed after veri
- Qualified ACPX runtime version omitted its package
- ACPX claude package omitted its qualified dependencies
- ACPX claude package dependency mismatch for ${expected.packa
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/e38b085b5cd3a025.
Report an issue: GitHub.