Skip to content

`npm install -g` fails with `EBUSY … unlink '.nfsXXXX'` (qmd upgrade)

Source: Troubleshooting reference

bootstrap.sh upgrade (or install/node.sh) dies upgrading a global npm package — almost always @tobilu/qmd:

npm error code EBUSY
npm error EBUSY: resource busy or locked, unlink
'.../@tobilu/qmd/node_modules/sqlite-vec-linux-x64/.nfs000000001f79d0f000015a88'
[fail] node.sh failed

Root cause: NFS “silly-rename”. The qmd MCP daemon (qmd mcp --http --port 8181) keeps native addons (sqlite-vec, node-llama-cpp, better-sqlite3) mmap’d. When npm deletes the old package tree to swap in the new one, NFS can’t remove a file the daemon still has open, so it renames it to .nfsXXXX and keeps it until that fd closes. npm then can’t unlink the .nfs* file and aborts with EBUSY. Only happens on NFS homes (the Linux clusters) — macOS local disks unlink open files fine, so this is gated to Linux.

node.sh now stops the daemon around the qmd upgrade and restarts it (via the qmd_daemon_* helpers in _lib.sh), so a normal upgrade no longer trips on it. To recover a checkout that predates the fix, or if you hit it by hand:

Terminal window
pkill -f "qmd[^ ]* mcp --http" # 1. stop the daemon → NFS reaps .nfs* files
npm install -g @tobilu/qmd@latest # 2. re-run the upgrade (or: bash install/node.sh)
qmd mcp --http --daemon & # 3. restart (a new shell also lazy-starts it)

A failed swap can also leave a broken husk — a qmd/ dir with only an empty node_modules/ plus a dangling bin/qmd symlink — in a different npm prefix than the one which qmd resolves to (nvm’s). Delete the husk; the live copy is the one on PATH.