Skip to content

pnpm deploy fails with "Operation not permitted" inside user-namespace containers (idmapped mounts): FICLONE denial (EPERM) is fatal instead of falling back to copy #14722

Description

@Basielienis

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pnpm version

No response

Which area(s) of pnpm are affected? (leave empty if unsure)

Package manager compatibility

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

Inside an unprivileged LXC container (user namespace, idmapped mounts) — e.g. a Proxmox LXC with unprivileged: 1, or rootless podman:

mkdir -p ws/packages/app && cd ws
printf 'packages:\n - "packages/*"\n' > pnpm-workspace.yaml
echo '{"name":"repro","version":"0.0.0","private":true,"packageManager":"pnpm@12.3.4"}' > package.json
echo '{"name":"app","version":"0.0.0","private":true}' > packages/app/package.json
npx pnpm@12.3.4 --filter app deploy /tmp/out --legacy

Fails on 12.3.4; the same host with pnpm 11.x succeeds. Also fails without --legacy. pnpm install in the same workspace succeeds.

Describe the Bug

pnpm deploy aborts in the "copy project files" phase with Operation not permitted (os error 1) at an arbitrary file (varies per run), e.g.:

× copy project files
╰─▶ failed to import "/…/packages/app/package.json" to
"/tmp/out_pacquet-stage_…/package.json_pacquet-stage_…": Operation not permitted (os error 1)

Diagnosis (probed empirically on the failing host):

Inside a user-namespace container the kernel rejects remap_file_range (the FICLONE path) with EPERM — by design for idmapped mounts — independent of filesystem:

probe (same host) result
FICLONE, container process, ZFS→ZFS EPERM
FICLONE, container process, tmpfs→tmpfs (tmpfs cannot clone at all) EPERM — denial happens before any fs is consulted
FICLONE, container root EPERM
FICLONE, host process, same dataset works
cp --reflink=auto in container OK (GNU cp falls back to copy)

pnpm 12's Rust import strategy falls back on ENOTSUP (→hardlink) and EXDEV (→copy, sticky), but treats EPERM from the clone attempt as fatal — so deploy dies at whichever file the walk imports when the strategy attempts the clone.

A contrast that pinpoints it: pnpm install works in the same container — v12 changed store imports to hardlink-first on Linux, and store→workspace hardlinks are same-fs → they succeed. The deploy project-file importer, however, hits the clone.

--config.package-import-method=copy does not help: it governs store→node_modules imports, not the deploy project-file copy.

This makes pnpm deploy unusable in every user-namespace container (unprivileged LXC, rootless podman, CI sandboxes) — a fairly common production environment class.

Expected Behavior

A failed clone attempt (EPERM) should fall back to hardlink/copy just like ENOTSUP/EXDEV does — ideally probing once and permanently switching the strategy for the rest of the operation, as the EXDEV fallback already does.

Which Node.js version are you using?

v24.20.0

Which operating systems have you used?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

Debian 13 (trixie) inside an unprivileged LXC (Proxmox), host kernel 7.0.2-7-pve

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions