{"record":{"id":"21c7ed1ffadb3dcf","repo":"paperclipai/paperclip","slug":"acpx-package-snapshot-exceeds-its-file-bound","errorCode":null,"errorMessage":"ACPX package snapshot exceeds its file bound","messagePattern":"ACPX package snapshot exceeds its file bound","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/private-snapshot.ts","lineNumber":99,"sourceCode":"    await rm(directory, { recursive: true, force: true });\n  };\n  const mapPath = (source: string): string | null => {\n    const candidates = sourceRoots\n      .map((root, index) => ({ root, index }))\n      .filter(({ root }) => within(root, source))\n      .sort((a, b) => b.root.length - a.root.length);\n    const match = candidates[0];\n    return match\n      ? resolve(roots[match.index]!, relative(match.root, source))\n      : null;\n  };\n  const copy = async (\n    source: string,\n    target: string,\n    root: string,\n  ): Promise<void> => {\n    if (++filesCopied > 30_000)\n      throw new Error(\"ACPX package snapshot exceeds its file bound\");\n    const before = await lstat(source, { bigint: true });\n    if (before.isSymbolicLink()) {\n      const canonical = await realpath(source);\n      const mapped = mapPath(canonical);\n      // Package-manager links to unqualified packages do not grant import authority.\n      if (mapped) await symlink(mapped, target);\n      return;\n    }\n    if (!within(root, await realpath(source)))\n      throw new Error(\"ACPX snapshot escaped its package\");\n    if (before.isDirectory()) {\n      await mkdir(target, { mode: 0o700 });\n      directories.push(target);\n      for (const entry of await readdir(source))\n        await copy(join(source, entry), join(target, entry), root);\n      if (!same(before, await lstat(source, { bigint: true })))\n        throw new Error(\"ACPX package directory changed during snapshot\");\n      return;","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/private-snapshot.ts#L81-L117","documentation":"The recursive copy inside createAcpxPrivateSnapshot enforces a hard cap of 30,000 files per package snapshot. When the number of copied files exceeds this bound the library aborts with this error, refusing to snapshot an unbounded package tree. This is a deliberate resource-exhaustion guard, not an I/O failure.","triggerScenarios":"Calling createAcpxPrivateSnapshot on a package root whose recursive file count exceeds 30,000 (filesCopied counter increments on every copy invocation, including nested directories and symlinks).","commonSituations":"Admitting an oversized monorepo package (e.g. a bundled node_modules inside the package, test fixtures, or generated assets) as an ACPX package root; accidentally pointing the snapshot at node_modules instead of a single package.","solutions":["Point the package root at the specific package directory, not a parent containing node_modules or build output","Exclude generated artifacts, fixtures, and vendored trees from the package root before snapshotting","Split the dependency into smaller admitted package roots if legitimately huge","If you maintain the runner and truly need more files, raise the 30_000 bound consciously, accepting the resource cost"],"exampleFix":"// before\nroots: [workspaceRoot]\n// after\nroots: [join(workspaceRoot, 'packages/my-app')]","handlingStrategy":"validation","validationCode":"async function countFiles(root: string): Promise<number> {\n  let n = 0;\n  const stack = [root];\n  while (stack.length) {\n    const dir = stack.pop()!;\n    for (const e of await fs.readdir(dir, { withFileTypes: true })) {\n      if (++n > 30_000) throw new Error(`package too large: ${n} files`);\n      if (e.isDirectory()) stack.push(path.join(dir, e.name));\n    }\n  }\n  return n;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await createAcpxPrivateSnapshot({ roots: [root] });\n} catch (e) {\n  if (e.message.includes('exceeds its file bound')) {\n    throw new Error(`Admitted root ${root} has >30k files; narrow the root`);\n  }\n  throw e;\n}","preventionTips":["Always admit the individual package directory, never node_modules or the repo root","Prune generated assets, fixtures, and vendored trees from packaged roots","Count files (pre-flight) before admitting a new package root"],"tags":["filesystem","limit","snapshot"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}