Skip to content

fix(install): handle ETXTBSY when setting up node_modules#33311

Open
bartlomieju wants to merge 1 commit intomainfrom
fix/etxtbsy-hardlink
Open

fix(install): handle ETXTBSY when setting up node_modules#33311
bartlomieju wants to merge 1 commit intomainfrom
fix/etxtbsy-hardlink

Conversation

@bartlomieju
Copy link
Copy Markdown
Member

Summary

Fixes ETXTBSY ("Text file busy", OS error 26) errors when using nodeModulesDir: "auto" on Linux, especially in containers (Podman), CI, WSL2, and when the LSP or a dev server is running alongside.

Root cause: With nodeModulesDir: "auto", Deno creates hardlinks from the global npm cache to node_modules/.deno/. When a binary (like esbuild) is executing from one of these hardlinks, any attempt to write to the same inode fails with ETXTBSY. This affects:

  • vite/vitepress builds that spawn esbuild
  • Fresh plugin-vite patches
  • deno install while another Deno process is running

Fix: When a copy operation encounters ETXTBSY, remove the destination file first (breaking the hardlink), then retry. unlink() works on executing files — it removes the directory entry while the process keeps the inode alive — so the retry creates a fresh inode.

Three locations are patched:

  • copy_dir_recursive() — the fallback when hard_link_dir_recursive fails
  • clone_dir_recursive_except_node_modules_child() — individual file hardlink-or-copy in local.rs
  • Added is_etxtbsy() helper to deno_npm_cache

Closes #30424

Test plan

  • cargo check passes
  • cargo clippy --all-targets clean
  • cargo test -p deno_npm_cache -p deno_npm_installer passes
  • Manually verified on Linux with executing esbuild binary (needs CI / contributor with Linux)

🤖 Generated with Claude Code

When `nodeModulesDir: "auto"` is used, Deno creates hardlinks from the
global npm cache to `node_modules/.deno/`. If a binary (like esbuild) is
currently executing from one of these hardlinks, attempts to overwrite
the file fail with ETXTBSY ("text file busy") on Linux.

This manifests as "Text file busy (os error 26)" errors during
vite/vitepress builds, especially in containers (Podman/Docker), CI, and
when the LSP or a dev server is running alongside.

The fix handles ETXTBSY in the copy fallback paths by removing the
destination file before retrying the copy. Removing the file (unlink)
breaks the hardlink and allows the copy to create a new inode that
doesn't conflict with the executing process.

Closes #30424

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
// The destination is a hardlink to a currently-executing
// binary (ETXTBSY). Remove it to break the hardlink, then
// retry the copy.
let _ = sys.fs_remove_file(&new_to);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Problem: the ETXTBSY recovery path ignores fs_remove_file errors before retrying the copy.

Why here: in both fallback paths we do let _ = sys.fs_remove_file(&new_to); and then immediately retry fs_copy. If the unlink fails for a reason other than "already gone" (permissions, read-only fs, unexpected race), we silently discard the more informative error and only surface whatever the second copy returns.

Impact: that can make ETXTBSY-related setup failures harder to diagnose, especially on the container / overlayfs configurations this PR is targeting.

Ask: would it make sense to only ignore NotFound here and otherwise return the unlink error directly? That would still handle the intended race while preserving better failure information for the weird cases.

@bartlomieju bartlomieju requested a review from nathanwhit April 21, 2026 07:03
Copy link
Copy Markdown
Member

@nathanwhit nathanwhit left a comment

Choose a reason for hiding this comment

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

I'm not super convinced it will fix anything, but seems ok to me

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.

VitePress fails to build

3 participants