Fix react-scripts: Permission denied on npm run build
Find out in two commands whether your react-scripts bin lost its execute bit or lives on a noexec filesystem, then apply the fix that matches, instead of chmod 777 and sudo.
TL;DR#
npm run build runs react-scripts build through sh, with node_modules/.bin on the PATH. sh: 1: react-scripts: Permission denied means the shell found the command and the kernel refused to execute it. There are two ways to get there. Either the file behind node_modules/.bin/react-scripts lost its execute bit after npm installed it, because node_modules was committed to git, copied or unpacked from somewhere else, or touched by a chmod. Or the project lives on a filesystem mounted noexec, where no mode helps. In the first case, delete node_modules and install again (or chmod +x node_modules/.bin/react-scripts). In the second, move the workspace off that mount, or call the script through node so nothing has to be executed directly. sudo fixes neither.
The error from the Jenkins job#
The Stack Overflow question (46 votes, about 150,000 views) comes from a Spring Boot and React app moved from Windows 10 to Ubuntu 18.04 and built by Jenkins. A manual npm run build worked once, then the Jenkins "Build Now" failed. The relevant lines of its log, with the console colour codes removed:
> react-scripts build
sh: 1: react-scripts: Permission denied
npm ERR! code ELIFECYCLE
npm ERR! errno 126
npm ERR! medaverter-front@0.1.0 build: `react-scripts build`That output is from an older npm. A current npm prints the same shell line without the ELIFECYCLE block. Here is the same failure on npm 10.9.9:
> medaverter-front@0.1.0 build
> react-scripts build
sh: 1: react-scripts: Permission deniedWhy the shell refuses to run react-scripts#
npm's documentation on scripts says that executables from your dependencies "will be added to the PATH for executing the scripts", and that such a script "is exported into the node_modules/.bin directory on npm install". So react-scripts build in package.json is a plain shell command. The shell walks the PATH, finds node_modules/.bin/react-scripts, and asks the kernel to execute it.
On Linux that entry is a symbolic link into the package, as a fresh install shows:
lrwxrwxrwx 1 root root 37 Oct 13 00:54 node_modules/.bin/react-scripts -> ../react-scripts/bin/react-scripts.js
-rwxr-xr-x 1 root root 1895 Oct 13 00:54 node_modules/react-scripts/bin/react-scripts.jsThe target is a JavaScript file with a #!/usr/bin/env node line at the top. The kernel will only start it if the file has the execute bit (the x in -rwxr-xr-x) and sits on a filesystem that allows execution. npm sets the bit itself when it installs the package. The code that does it, lib/fix-bin.js in npm's bin-links package, opens with this comment:
// make sure that bins are executable, and that they don't have
// windows line-endings on the hashbang line.npm's installer, @npmcli/arborist, lists bin-links among its dependencies. "Permission denied" therefore means one of two things happened after npm did its part:
- The mode was lost. Git records a file as
100644(not executable) or100755(executable), and a checkout writes the file with the recorded mode. The git-config documentation for core.fileMode warns that "Some filesystems lose the executable bit when a file that is marked as executable is checked out". Anode_modulescommitted from a machine or filesystem that loses the bit, unpacked from an archive, or copied through a filesystem that does not keep modes can come back without thex. - The mount forbids execution. A filesystem mounted with the
noexecoption refuses to execute any file, whatever its mode. If the build workspace sits on such a mount, nochmodhelps.
A file the shell cannot find gives a different message, not found instead of Permission denied, like the babel-node: not found error on Heroku, where the package had been pruned. Permission denied means the file is there.
Reproducing both causes#
Both causes were reproduced on 13 October 2026 in the official node:22-bookworm Docker image (Node 22.23.3, npm 10.9.9, Debian 12.15), as root, with CI=true. It used a minimal app on react-scripts 5.0.1 rather than the question's 3.4.0. The baseline npm run build printed Compiled successfully. with exit code 0.
Execute bit removed. After chmod -x on the bin target, npm run build failed even though the shell runs as root:
-rw-r--r-- 1 root root 1895 Oct 13 00:54 node_modules/react-scripts/bin/react-scripts.js
> medaverter-front@0.1.0 build
> react-scripts build
sh: 1: react-scripts: Permission denied
exit=127Committed to git without the bit. This is the cross-platform case. The project, node_modules included, was committed with the target changed in the index from 100755 to 100644 (as a machine that loses the bit would record it), then cloned:
100755 09604f6a03f7df6295cc593ff4e76af6d45bef0c 0 node_modules/react-scripts/bin/react-scripts.js
100644 09604f6a03f7df6295cc593ff4e76af6d45bef0c 0 node_modules/react-scripts/bin/react-scripts.js
-rw-r--r-- 1 root root 1895 Oct 13 01:10 node_modules/react-scripts/bin/react-scripts.jsThe clone has the same content but mode 644, and its build fails with the same Permission denied line.
Project on a noexec mount. The same working project was copied to a tmpfs mounted noexec, and every file in node_modules was given mode 777, which is what the asker did:
/noexec rw,nosuid,nodev,noexec,relatime,size=2097152k
-rwxrwxrwx 1 root root 1895 Oct 13 00:57 node_modules/react-scripts/bin/react-scripts.js
> medaverter-front@0.1.0 build
> react-scripts build
sh: 1: react-scripts: Permission denied
exit=126The exit code does not tell the two causes apart reliably. In these runs npm returned 127 for the missing bit and 126 on the noexec mount, while the question shows 126. Look at the file mode and the mount, not at the number.
Fix#
1. Find out which cause you have#
Run these from the project root, as the same user and on the same machine as the failing build (in Jenkins, as a shell step of the job):
ls -lL node_modules/.bin/react-scripts
findmnt -no OPTIONS -T node_modules/.binls -lL follows the link and shows the mode of the real file. If it reads -rw-r--r--, the bit is missing. If findmnt lists noexec among the options, the mount is the cause. Inside a container the option list is long, so the test filtered it down to the options that matter:
ls -lL node_modules/.bin/react-scripts
findmnt -no OPTIONS -T node_modules/.bin | tr , "\n" | grep -x -e rw -e ro -e noexecIn the git case above, that printed the missing bit and a mount without noexec:
-rw-r--r-- 1 root root 1895 Oct 13 01:10 node_modules/.bin/react-scripts
rw2. If the execute bit is missing: reinstall#
rm -rf node_modules
npm installnpm links the bins again and sets the mode. In the git case this brought the file back to -rwxr-xr-x and the build printed Compiled successfully. With a lockfile that matches package.json, npm ci does the same; its documentation says it is "meant to be used in automated environments such as test platforms, continuous integration, and deployment".
A one-line patch also works, because chmod follows the symbolic link to the real file:
chmod +x node_modules/.bin/react-scriptsThis is the second solution of the highest-voted answer, and it worked in the test. It only fixes that one bin, though. Any other package bin that lost its mode the same way will fail next.
3. Stop committing node_modules#
If the bit came back missing after a checkout, the repository is the source. Remove the folder from git and let the build install it:
git rm -r --cached node_modules
echo "node_modules/" >> .gitignore
git commit -m "Stop tracking node_modules"Then start the Jenkins job with rm -rf node_modules && npm install, before npm run build. Deleting the folder matters: a reused workspace keeps the old node_modules, and in the test a plain npm install over the existing folder left the file at mode 644, while the delete-and-install sequence restored the bit in the git case. Once your committed lockfile is in sync with package.json, npm ci does the same job, since it removes an existing node_modules itself and refuses to run when the lockfile is out of sync.
4. If the mount is noexec: move the workspace, or go through node#
The clean fix is to keep the build workspace on a filesystem that allows execution: move the job's workspace to another disk, or have the mount changed by whoever administers the agent. If you cannot change the mount, run the script through the node binary. Node reads the JavaScript file instead of executing it, so the mount option does not apply:
{
"scripts": {
"build": "node node_modules/react-scripts/bin/react-scripts.js build"
}
}On the noexec mount, this command built the app with exit code 0 and Compiled successfully., while npm run build with the default script kept failing.
Verify the fix#
Run the build the same way the failing job does and check both the file and the result. The test ran these lines after the reinstall in the git case, with the build output sent to a file and only its first Compiled line printed:
ls -l node_modules/react-scripts/bin/react-scripts.js
npm run build > /tmp/b.log 2>&1; echo "exit=$?"; grep -m1 "Compiled" /tmp/b.logThe mode should start with -rwx, and react-scripts should reach its own output:
-rwxr-xr-x 1 root root 1895 Oct 13 01:10 node_modules/react-scripts/bin/react-scripts.js
exit=0
Compiled successfully.Once the shell can start react-scripts, the errors that follow come from react-scripts itself. It checks for public/index.html and src/index.js before it compiles anything. If a missing file stops the build in Docker, the page on Could not find a required file: index.html explains why. react-scripts 3.4.0, the version in the question, depends on webpack 4 according to its registry entry. If such a build now fails with error:0308010C:digital envelope routines::unsupported, continue with the 0308010C fix, which covers webpack 4 on Node 17 and later.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
What the answers on the question really do#
The question's answers all aim at the same symptom. Tested one at a time against the missing-bit case, they behave very differently:
npm install react-scripts --save(the first suggestion of the highest-voted answer) did nothing here. npm reportedup to date in 3s, the file stayed at-rw-r--r--, and the exact pin was rewritten to"react-scripts": "^5.0.1". A plainnpm install,npm rebuild react-scriptsand a barenpm rebuildalso left the mode at 644.chmod +x node_modules/.bin/react-scriptsworked, as shown above.sudo npm run buildcannot help. The whole test ran as root, and root got the samePermission denied. Running npm through sudo also leaves root-owned files behind, which leads to npm EACCES errors for your own user. The same answer's advice tochmod -R 777the npm binary does nothing for a file inside your project.rm -rf node_modulesthen a reinstall (the third answer) worked, because the reinstall sets the mode again.
None of them fixes a noexec mount, where only a different workspace or the node call works.
Two steps in the question deserve a note. npm install -g run inside the project does not install the project's dependencies. npm's install documentation says that in global mode "it installs the current package context (ie, the current working directory) as a global package." The other is chmod -R 777: it hides a missing bit on your machine, but the next fresh checkout brings the git modes back. The log in the question does not show which cause the Jenkins workspace had, so check the mode and the mount on the agent itself. For a different permission error, where a non-root container user cannot write into a root-owned node_modules, see EACCES: permission denied, mkdir node_modules/.cache.
Further reading: Fix Cannot find module 'caniuse-lite/dist/unpacker/agents'.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Material UI install fails on React 18 or 19 (ERESOLVE): fix
See why npm refuses the old @material-ui packages on a React 18 or 19 project, which packages to install instead, and why --legacy-peer-deps on React 19 only moves the failure to runtime.
Fix Cannot find module 'caniuse-lite/dist/unpacker/agents'
Why npm install, npm ls and update-browserslist-db all report a healthy tree while the error stays, and the one reinstall that actually puts the missing files back.
Fix npm EACCES: permission denied on package-lock.json
Separate a local lockfile permission failure from cache and global-install errors, then fix the owner without changing the dependency tree.


