Skip to content

fix: use copy package-import-method in Docker to avoid pnpm ENOENT#12100

Merged
0xApotheosis merged 3 commits intodevelopfrom
fix/docker-pnpm-enoent
Mar 5, 2026
Merged

fix: use copy package-import-method in Docker to avoid pnpm ENOENT#12100
0xApotheosis merged 3 commits intodevelopfrom
fix/docker-pnpm-enoent

Conversation

@0xApotheosis
Copy link
Member

@0xApotheosis 0xApotheosis commented Mar 5, 2026

Description

Fix intermittent pnpm install failure in Docker builds for both public-api and swap-widget:

ERR_PNPM_ENOENT  ENOENT: no such file or directory, rename 'node_modules/hdkey/node_modules/_tmp_1_860f82da213975c76313830e4cd293e8' -> 'node_modules/hdkey/node_modules/secp256k1'

pnpm's default hard-link strategy (package-import-method=auto) races with Docker's overlay filesystem during atomic renames. Setting NPM_CONFIG_PACKAGE_IMPORT_METHOD=copy via env var in the Dockerfile tells pnpm to copy packages instead of hard-linking, avoiding the race condition. This only affects Docker builds — local dev continues using the .npmrc defaults.

Issue (if applicable)

Follow-up to #12099 — swap-widget deploy started failing after that merge

Risk

Low. Only changes Docker build behavior for pnpm install. No runtime changes. Uses a well-documented pnpm configuration option.

No protocols, transaction types, wallets or contract interactions are affected.

Testing

Engineering

  1. Merge and verify Railway builds succeed for public-api and swap-widget
  2. The pnpm install step should complete without ENOENT errors

Operations

  • 🏁 Infrastructure-only change, no user-facing impact. No operations testing needed.

Screenshots (if applicable)

N/A

Summary by CodeRabbit

  • Chores
    • Improved Docker build reliability for public API and swap widget services.
    • Changed package import behavior to avoid filesystem rename failures during image builds.
    • Added a resilient install process that retries package installation (with cleanup between attempts) and logs retry attempts to reduce flaky build failures.
  • New Features
    • Added TypeScript type support for the swap widget build environment.

pnpm's default hard-link strategy fails intermittently on Docker's
overlay filesystem with "ENOENT: rename _tmp -> secp256k1". Setting
package-import-method=copy via env var (Docker-only, doesn't affect
local dev .npmrc) fixes the atomic rename race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@0xApotheosis 0xApotheosis requested a review from a team as a code owner March 5, 2026 02:04
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22adfa16-d445-4a46-8b05-0aa30735684b

📥 Commits

Reviewing files that changed from the base of the PR and between d0da315 and aa9b1ea.

📒 Files selected for processing (1)
  • packages/swap-widget/tsconfig.json

📝 Walkthrough

Walkthrough

Adds package import method copy and a three-attempt retry loop around pnpm install in two Dockerfiles; also adds vite/client type reference to packages/swap-widget/tsconfig.json.

Changes

Cohort / File(s) Summary
Dockerfiles (install behavior)
packages/public-api/Dockerfile, packages/swap-widget/Dockerfile
Adds ENV NPM_CONFIG_PACKAGE_IMPORT_METHOD=copy and replaces a single pnpm install with a retry loop (up to 3 attempts), removing node_modules between failed attempts and logging retries; preserves --frozen-lockfile and --ignore-scripts and retains existing patch/copy steps.
Type config
packages/swap-widget/tsconfig.json
Adds "types": ["vite/client"] to include Vite client type definitions.

Sequence Diagram(s)

(omitted — changes are build/setup focused and do not introduce a multi-component runtime control flow requiring a sequence diagram)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • gomesalexandre
  • NeOMakinG

Poem

🐰 I nibble logs and hop with cheer,

Copies take the place of links so dear,
Three gentle tries before I stop,
Clearing crumbs that made builds flop,
Now containers hum without a fear.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: using copy package-import-method in Docker to resolve pnpm ENOENT errors, which aligns with all three file modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/docker-pnpm-enoent

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The ENOENT rename failure persists even with package-import-method=copy
because pnpm's hoisting/linking phase still does atomic renames that
race on Docker's overlay filesystem. Since it's intermittent and all
packages are cached in the store after the first attempt, a retry with
clean node_modules is fast and reliable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/swap-widget/Dockerfile`:
- Around line 61-64: The retry loop using "for i in 1 2 3; do ... pnpm install
--frozen-lockfile --ignore-scripts && break || { echo ...; rm -rf node_modules;
}; done" can return success even if all pnpm attempts fail because the cleanup
command (rm -rf node_modules) exits 0; change the failure branch so that a
failed pnpm install yields a non-zero exit on the final attempt (e.g., after
cleaning on failure either return a non-zero status or explicitly exit when
i==3) so the Docker build fails if all retries fail.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c02e691-5ee5-4b6b-956b-92caaf000510

📥 Commits

Reviewing files that changed from the base of the PR and between 71157a7 and d0da315.

📒 Files selected for processing (2)
  • packages/public-api/Dockerfile
  • packages/swap-widget/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/public-api/Dockerfile

Comment on lines +61 to +64
for i in 1 2 3; do \
pnpm install --frozen-lockfile --ignore-scripts && break || \
{ echo "pnpm install attempt $i failed, retrying..."; rm -rf node_modules; }; \
done
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's verify the shell semantics claim from the review
# Test the bad pattern (current implementation)
sh -c 'for i in 1 2 3; do false && break || { rm -rf /tmp/nonexistent; }; done'
echo "bad loop exit=$?"

# Test the good pattern (proposed fix)
sh -c 'for i in 1 2 3; do if false; then break; fi; if [ "$i" -eq 3 ]; then exit 1; fi; done'
echo "good loop exit=$?"

Repository: shapeshift/web

Length of output: 90


🏁 Script executed:

# Now let's check the actual Dockerfile to confirm the code matches the review comment
cd packages/swap-widget && cat -n Dockerfile | head -70 | tail -20

Repository: shapeshift/web

Length of output: 1302


Retry loop falsely succeeds after three failed installs.

The current retry loop at lines 61-64 can exit successfully even when all pnpm install attempts fail. When pnpm fails on the final iteration, the || clause executes rm -rf node_modules, which exits with code 0, causing the loop to complete without error. This allows Docker builds to succeed despite missing dependencies, risking broken container images being pushed to production.

Proposed fix
 RUN corepack enable && corepack prepare pnpm@10.30.3 --activate && \
-    for i in 1 2 3; do \
-      pnpm install --frozen-lockfile --ignore-scripts && break || \
-      { echo "pnpm install attempt $i failed, retrying..."; rm -rf node_modules; }; \
-    done
+    for i in 1 2 3; do \
+      if pnpm install --frozen-lockfile --ignore-scripts; then \
+        break; \
+      fi; \
+      if [ "$i" -eq 3 ]; then \
+        echo "pnpm install failed after $i attempts"; \
+        exit 1; \
+      fi; \
+      echo "pnpm install attempt $i failed, retrying..."; \
+      rm -rf node_modules; \
+    done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i in 1 2 3; do \
pnpm install --frozen-lockfile --ignore-scripts && break || \
{ echo "pnpm install attempt $i failed, retrying..."; rm -rf node_modules; }; \
done
RUN corepack enable && corepack prepare pnpm@10.30.3 --activate && \
for i in 1 2 3; do \
if pnpm install --frozen-lockfile --ignore-scripts; then \
break; \
fi; \
if [ "$i" -eq 3 ]; then \
echo "pnpm install failed after $i attempts"; \
exit 1; \
fi; \
echo "pnpm install attempt $i failed, retrying..."; \
rm -rf node_modules; \
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swap-widget/Dockerfile` around lines 61 - 64, The retry loop using
"for i in 1 2 3; do ... pnpm install --frozen-lockfile --ignore-scripts && break
|| { echo ...; rm -rf node_modules; }; done" can return success even if all pnpm
attempts fail because the cleanup command (rm -rf node_modules) exits 0; change
the failure branch so that a failed pnpm install yields a non-zero exit on the
final attempt (e.g., after cleaning on failure either return a non-zero status
or explicitly exit when i==3) so the Docker build fails if all retries fail.

Without an explicit `types` field, TypeScript auto-discovers all
@types/* packages hoisted to root node_modules. The deprecated stub
@types/ethereumjs-util (from hdwallet-ledger devDeps) has no .d.ts
files, causing TS2688 during Docker builds. Restricting to
["vite/client"] matches the pattern used by the root tsconfig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to widget / develop March 5, 2026 02:53 Inactive
Copy link
Member Author

@0xApotheosis 0xApotheosis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the swap widget now deploys from this branch:

Image

@0xApotheosis 0xApotheosis merged commit 3c56acf into develop Mar 5, 2026
5 checks passed
@0xApotheosis 0xApotheosis deleted the fix/docker-pnpm-enoent branch March 5, 2026 03:44
0xApotheosis added a commit that referenced this pull request Mar 5, 2026
…12100)

* fix: use copy package-import-method in Docker to avoid pnpm ENOENT

pnpm's default hard-link strategy fails intermittently on Docker's
overlay filesystem with "ENOENT: rename _tmp -> secp256k1". Setting
package-import-method=copy via env var (Docker-only, doesn't affect
local dev .npmrc) fixes the atomic rename race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add retry loop for pnpm install in Docker builds

The ENOENT rename failure persists even with package-import-method=copy
because pnpm's hoisting/linking phase still does atomic renames that
race on Docker's overlay filesystem. Since it's intermittent and all
packages are cached in the store after the first attempt, a retry with
clean node_modules is fast and reliable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restrict swap-widget tsconfig types to prevent auto-discovery

Without an explicit `types` field, TypeScript auto-discovers all
@types/* packages hoisted to root node_modules. The deprecated stub
@types/ethereumjs-util (from hdwallet-ledger devDeps) has no .d.ts
files, causing TS2688 during Docker builds. Restricting to
["vite/client"] matches the pattern used by the root tsconfig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant