Title: fs.mkdir(path, { recursive: true }) never returns when an intermediate mkdir fails with ENOENT although its parent exists (e.g. under /proc) · Issue #66890 · nodejs/node · GitHub
Open Graph Title: fs.mkdir(path, { recursive: true }) never returns when an intermediate mkdir fails with ENOENT although its parent exists (e.g. under /proc) · Issue #66890 · nodejs/node
X Title: fs.mkdir(path, { recursive: true }) never returns when an intermediate mkdir fails with ENOENT although its parent exists (e.g. under /proc) · Issue #66890 · nodejs/node
Description: Version v24.18.0 and v22.22.2 (both reproduce) Platform Linux 7.0.0-38-generic x86_64, Ubuntu 26.04.1 LTS; libuv 1.52.1 Subsystem fs What steps will reproduce the bug? # sync: never returns, main thread at 100 % CPU node -e "require('fs'...
Open Graph Description: Version v24.18.0 and v22.22.2 (both reproduce) Platform Linux 7.0.0-38-generic x86_64, Ubuntu 26.04.1 LTS; libuv 1.52.1 Subsystem fs What steps will reproduce the bug? # sync: never returns, main t...
X Description: Version v24.18.0 and v22.22.2 (both reproduce) Platform Linux 7.0.0-38-generic x86_64, Ubuntu 26.04.1 LTS; libuv 1.52.1 Subsystem fs What steps will reproduce the bug? # sync: never returns, main t...
Opengraph URL: https://github.com/nodejs/node/issues/66890
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"fs.mkdir(path, { recursive: true }) never returns when an intermediate mkdir fails with ENOENT although its parent exists (e.g. under /proc)","articleBody":"### Version\n\nv24.18.0 and v22.22.2 (both reproduce)\n\n### Platform\n\nLinux 7.0.0-38-generic x86_64, Ubuntu 26.04.1 LTS; libuv 1.52.1\n\n### Subsystem\n\nfs\n\n### What steps will reproduce the bug?\n\n```sh\n# sync: never returns, main thread at 100 % CPU\nnode -e \"require('fs').mkdirSync('/proc/nope/deeper', { recursive: true })\"\n\n# async: the promise never settles, process never exits, CPU busy\nnode -e \"require('fs/promises').mkdir('/proc/nope/deeper', { recursive: true }).then(() => console.log('ok'), (e) => console.log('err', e.code))\"\n```\n\nBoth hang until killed (`timeout 20 node ...` exits 124).\n\nThe precondition is a filesystem where `mkdir` of a child fails with `ENOENT` while the parent exists and is a directory. procfs does exactly that:\n\n```\n$ mkdir /proc/nope\nmkdir: cannot create directory '/proc/nope': No such file or directory\n```\n\nsysfs answers `EACCES` instead and the call fails fast, as expected. A plain `fs.mkdirSync('/proc/nope')` (non-recursive) also fails fast with `ENOENT`.\n\n### How often does it reproduce? Is there a required condition?\n\nAlways, on any path whose intermediate component cannot be created and whose filesystem reports `ENOENT` for that `mkdir` (procfs; presumably some FUSE / network filesystems too).\n\n### What is the expected behavior? Why is that the expected behavior?\n\nReject / throw with the `mkdir` error (`ENOENT` here, as the non-recursive call does), the same way `EACCES`, `EPERM`, `ENOSPC` and `ENOTDIR` are already returned by the recursive implementation.\n\n### What do you see instead?\n\nThe call never completes. Sync: the main thread spins. Async: the request bounces between the event loop and the libuv threadpool forever — on a hung process we measured ~24.5 M voluntary context switches in 18 minutes (~22 k/s), RSS flat at ~40 MB, `wchan` `ep_poll`.\n\n### Additional information\n\nLooking at `src/node_file.cc` (v24.18.0):\n\n- `MKDirpSync` (L1759): in `case UV_ENOENT` (L1792) the failed path and its `dirname` are pushed back onto the continuation stack. The outer loop pops the parent (`/proc`), `uv_fs_mkdir` returns `EEXIST`, the `default:` branch `stat`s it, sees a directory and `break`s. The loop then pops `/proc/nope` again, `mkdir` returns `ENOENT` again, the same two paths are pushed again, and so on. Nothing records that the parent was just verified to exist, so the state machine cycles forever.\n- `MKDirpAsync` (L1827, `case UV_ENOENT` at L1873) has the same structure, so the async variant spins as well.\n\nA possible fix: when `mkdir(child)` returns `ENOENT` right after its parent was created or confirmed to be an existing directory, stop and return `ENOENT` instead of pushing the pair again (e.g. remember the last confirmed parent in the continuation data, or bound the number of `ENOENT` retries per path).\n\nHow we hit it: a test used `/proc/…/file.json` as an \"unwritable path\" for a fail-open on-disk store. The store's debounced write called `mkdir(dirname, { recursive: true })`; under host load the write fired before the `node --test` child exited, and the child spun for 2.3 hours in three separate CI runs before anyone noticed. Related earlier report about recursive mkdir error mapping: #47098.","author":{"url":"https://github.com/genlorem","@type":"Person","name":"genlorem"},"datePublished":"2026-10-21T08:56:57Z","url":"https://github.com/nodejs/node/issues/66890"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:6b4addd9-171d-ed7f-a0f8-b17fe7fc0c55 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CCB0:353AEB:17C097:2085A1:6AED4216 |
| html-safe-nonce | 9df21e978ac9a157fae630346622c971a405b09de97730106bdd165233f45980 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDQ0IwOjM1M0FFQjoxN0MwOTc6MjA4NUExOjZBRUQ0MjE2IiwidmlzaXRvcl9pZCI6Ijg2NTE4NDY1ODE3MjU1MTIyMTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 8267b2bbdee5528ae5227d22fd04937e7decb66c45ee78bbded239bdbd5bf789 |
| hovercard-subject-tag | issue:5957468319 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/66890/issue_layout |
| twitter:image | https://opengraph.githubassets.com/1a8938a2db6116294cecaec4bef8c6b54d474f44c478678a8289e0b85ef59e24/nodejs/node/issues/66890 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/1a8938a2db6116294cecaec4bef8c6b54d474f44c478678a8289e0b85ef59e24/nodejs/node/issues/66890 |
| og:image:alt | Version v24.18.0 and v22.22.2 (both reproduce) Platform Linux 7.0.0-38-generic x86_64, Ubuntu 26.04.1 LTS; libuv 1.52.1 Subsystem fs What steps will reproduce the bug? # sync: never returns, main t... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | genlorem |
| hostname | github.com |
| expected-hostname | github.com |
| None | 26c92a9388ddfad3fa88d132b5d5f0b4747030233dcc4e5efb05f1cbae152184 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | c629ad99c254d306375fc8354d04321c2c362236 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width