{"record":{"id":"2a3627abd28d989f","repo":"paperclipai/paperclip","slug":"acpx-executable-changed-during-snapshot","errorCode":null,"errorMessage":"ACPX executable changed during snapshot","messagePattern":"ACPX executable changed during snapshot","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/private-snapshot.ts","lineNumber":199,"sourceCode":"            if (error.code !== \"EEXIST\") throw error;\n          },\n        );\n      }\n    let executablePath: string | null = null;\n    if (executable) {\n      const before = await executable.stat({ bigint: true });\n      if (!before.isFile() || before.size < 1n || before.size > BigInt(MAX_ACPX_RUNTIME_EXECUTABLE_BYTES)) {\n        throw new Error(\"ACPX runtime executable must be a bounded executable file\");\n      }\n      // The bigint bound above makes this conversion exact before allocation.\n      const executableBytes = Number(before.size);\n      bytesCopied += executableBytes;\n      if (bytesCopied > MAX_SNAPSHOT_BYTES) {\n        throw new Error(\"ACPX snapshot exceeds its byte bound\");\n      }\n      const bytes = await readSnapshotBytes(executable, executableBytes);\n      if (!same(before, await executable.stat({ bigint: true })))\n        throw new Error(\"ACPX executable changed during snapshot\");\n      executablePath = join(directory, \"runtime\");\n      await writeFile(executablePath, bytes, { flag: \"wx\", mode: 0o500 });\n      digests[executablePath] = digest(bytes);\n    }\n    const manifest = Buffer.from(\n      JSON.stringify({ roots, executable: executablePath, digests }),\n    );\n    const manifestPath = join(directory, \"manifest.json\");\n    await writeFile(manifestPath, manifest, { flag: \"wx\", mode: 0o400 });\n    for (const dir of new Set(directories)) await chmod(dir, 0o500);\n    return {\n      roots,\n      executable: executablePath,\n      digests,\n      handoff: { path: manifestPath, digest: digest(manifest) },\n      close,\n    };\n  } catch (error) {","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/private-snapshot.ts#L181-L217","documentation":"After reading the executable's bytes, createAcpxPrivateSnapshot re-stats the source file and compares it to the stat taken before reading. If size/mtime/inode changed between the two stats, the executable was concurrently modified and the snapshot would be a torn, inconsistent copy, so it throws instead of writing a corrupt snapshot.","triggerScenarios":"Calling snapshot()/privateSnapshot() while another process (package manager, updater, build script, antivirus quarantine/rewrite) replaces or rewrites the ACPX runtime executable between the initial stat and the post-read verification stat.","commonSituations":"Running pnpm/yarn install or a build that relinks binaries in the same session; an auto-updater running concurrently; copying the repo while a tool rewrites timestamps; docker/CI image layers being mutated mid-run.","solutions":["Re-run the snapshot once the concurrent process (package manager, updater, build) has finished; the check is TOCTOU protection and usually succeeds on retry.","Identify what mutates the executable (lsof/fuser on the path) and stop it from running concurrently.","Pin/lock the ACPX runtime version so installs do not replace the binary mid-session.","Exclude the runtime directory from antivirus/indexer interference, or move the executable to a stable location referenced by config."],"exampleFix":"// before\nawait driver.snapshot(); // raced with `pnpm install` replacing the binary\n// after\nawait exec(\"pnpm install --frozen-lockfile\"); // quiesce first\nawait driver.snapshot();","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await driver.snapshot();\n} catch (e) {\n  if (e.message === \"ACPX executable changed during snapshot\") {\n    await waitForQuiescence(); // ensure no install/update is running\n    return retrySnapshot(3);\n  }\n  throw e;\n}","preventionTips":["Do not run pnpm install, updaters, or builds concurrently with ACPX sessions.","Pin the runtime binary version with a lockfile.","Exclude the runtime path from antivirus/backup tools that rewrite files."],"tags":["filesystem","race-condition","tocodeu","acpx"],"backgroundTag":"checksum-mismatch","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}