Commit c07db8c5c1b for nodejs
commit c07db8c5c1b1b18e1e0e088b83f56922c142c0cf
Author: Jason Zhang <xzha4350@gmail.com>
Date: Sun Nov 15 15:18:02 2026 +1030
src: avoid aborting on filesystem errors in node --run
`node --run` called the throwing overloads of
`std::filesystem::is_directory()` and `std::filesystem::current_path()`.
An unreadable `node_modules` directory or a removed working directory
made them throw, and the uncaught exception aborted the process.
Use the `std::error_code` overloads instead. A `node_modules/.bin` that
cannot be inspected is skipped, and a missing working directory is
reported as a regular error.
Signed-off-by: jazelly <xzha4350@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/67270
Fixes: https://github.com/nodejs/node/issues/67269
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com>
diff --git a/src/node_task_runner.cc b/src/node_task_runner.cc
index 136dec24fa0..b9716846abf 100644
--- a/src/node_task_runner.cc
+++ b/src/node_task_runner.cc
@@ -233,7 +233,9 @@ FindPackageJson(const std::filesystem::path& cwd) {
directory_path = directory_path.parent_path()) {
// Append "path/node_modules/.bin" to the env var, if it is a directory.
auto node_modules_bin = directory_path / "node_modules" / ".bin";
- if (std::filesystem::is_directory(node_modules_bin)) {
+ // Skip directories that can't be inspected (e.g. permission denied).
+ std::error_code error_code;
+ if (std::filesystem::is_directory(node_modules_bin, error_code)) {
path_env_var += ConvertPathToUTF8(node_modules_bin) + env_var_separator;
}
@@ -279,7 +281,15 @@ static void PrintScripts(FILE* out,
void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
std::string_view command_id,
const std::vector<std::string_view>& positional_args) {
- auto cwd = std::filesystem::current_path();
+ std::error_code error_code;
+ auto cwd = std::filesystem::current_path(error_code);
+ if (error_code) {
+ fprintf(stderr,
+ "Can't determine the current working directory: %s\n",
+ error_code.message().c_str());
+ result->exit_code_ = ExitCode::kGenericUserError;
+ return;
+ }
auto package_json = FindPackageJson(cwd);
if (!package_json.has_value()) {
diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js
index 57faa80b9f8..b276c54ca23 100644
--- a/test/parallel/test-node-run.js
+++ b/test/parallel/test-node-run.js
@@ -12,6 +12,8 @@ const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const envSuffix = common.isWindows ? '-windows' : '';
+tmpdir.refresh();
+
describe('node --run [command]', { concurrency: !process.env.TEST_PARALLEL }, () => {
it('returns error on non-existent file', async () => {
const child = await common.spawnPromisified(
@@ -336,4 +338,45 @@ describe('node --run [command]', { concurrency: !process.env.TEST_PARALLEL }, ()
assert.strictEqual(child.stdout, '');
assert.strictEqual(child.code, 1);
});
+
+ it('skips node_modules directories that cannot be inspected', {
+ skip: common.isWindows || process.getuid() === 0,
+ }, async () => {
+ const projectDir = tmpdir.resolve('unreadable-node-modules');
+ const nodeModules = path.join(projectDir, 'node_modules');
+ fs.mkdirSync(nodeModules, { recursive: true });
+ fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({
+ scripts: { test: 'echo ok' },
+ }));
+ fs.chmodSync(nodeModules, 0o000);
+ try {
+ const child = await common.spawnPromisified(
+ process.execPath,
+ [ '--run', 'test'],
+ { cwd: projectDir },
+ );
+ assert.strictEqual(child.stdout.trim(), 'ok');
+ assert.strictEqual(child.stderr, '');
+ assert.strictEqual(child.code, 0);
+ } finally {
+ fs.chmodSync(nodeModules, 0o755);
+ }
+ });
+
+ it('returns error when the current working directory is removed', {
+ // Windows, SunOS, AIX and IBM i refuse to remove a directory that is in
+ // use as a cwd.
+ skip: common.isWindows || common.isSunOS || common.isAIX || common.isIBMi,
+ }, async () => {
+ const removedDir = tmpdir.resolve('removed-cwd');
+ fs.mkdirSync(removedDir);
+ const child = await common.spawnPromisified(
+ '/bin/sh',
+ [ '-c', 'rmdir "$PWD" && exec "$0" --run test', process.execPath],
+ { cwd: removedDir },
+ );
+ assert.match(child.stderr, /Can't determine the current working directory/);
+ assert.strictEqual(child.stdout, '');
+ assert.strictEqual(child.code, 1);
+ });
});