Skip to content
Latchkey

npm Package "bin" Not Executable - Fix EACCES / Permission Denied on CLI in CI

npm marks a package’s declared bin files executable on install and symlinks them into node_modules/.bin. If a bin script lacks the executable bit - lost in a tarball, a Windows checkout, or a copied tree - invoking it fails with permission denied even though the file is present.

What this error means

A CLI installed as a dependency fails with Permission denied when run from node_modules/.bin, or a local package’s own bin cannot execute. The script exists and is correct; it is just not marked executable.

Terminal
$ ./node_modules/.bin/my-cli
sh: ./node_modules/.bin/my-cli: Permission denied
# or
npm error code 126
npm error command failed: my-cli (permission denied)

Diagnose it: reproduce the CI install locally

Install failures are usually environment drift rather than a broken lockfile: a different package-manager major, a different Node version, or a cache that is being restored from a run with different inputs. Reproduce the CI conditions before changing the lockfile, because regenerating it hides the real cause.

Terminal
# match the runner exactly, then install from a clean slate
node --version && npm --version
rm -rf node_modules
npm ci --foreground-scripts

# if that succeeds locally but fails in CI, the difference is the cache
# or the package-manager version, not your lockfile

Common causes

The bin file lost its executable bit

Archiving, a Windows-authored file, or copying a tree without preserving mode can strip the +x bit. Without it, the OS refuses to execute the script.

Running a local bin that was never chmod-ed

A package’s own bin script committed without the executable bit fails when run directly, since npm’s bin-linking relies on the file being executable.

How to fix it

Restore the executable bit

Mark the bin file executable, and run via npm/npx so the shebang is honored.

Terminal
chmod +x node_modules/.bin/my-cli   # or the package's bin source
# better: invoke through npm so .bin resolution applies
npx my-cli
# for your own package, commit the bit:
git update-index --chmod=+x bin/my-cli.js

Reinstall so npm re-links bins

  1. Run a clean npm ci so npm re-creates .bin links with correct modes.
  2. Ensure the bin file has a correct shebang (#!/usr/bin/env node).
  3. Avoid copying node_modules in a way that drops file modes.

Verify the fix survives a cold cache

A green run immediately after a fix often proves nothing, because it restored a cache written before the change. Force a cold install once to confirm the fix is real.

.github/workflows/ci.yml
# temporarily bust the cache key to prove the fix on a cold runner
- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: npm
    cache-dependency-path: package-lock.json
# then bump this suffix once, run, and remove it
#   key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}-v2

How to prevent it

  • Commit the executable bit on your package’s bin files.
  • Run CLIs via npx / npm scripts, not hard-coded .bin paths.
  • Reinstall clean so npm re-links bins with correct modes.

Frequently asked questions

What causes npm package "bin" not executable?
There are 2 common causes: the bin file lost its executable bit and running a local bin that was never chmod-ed. Archiving, a Windows-authored file, or copying a tree without preserving mode can strip the +x bit.
How do I fix npm package "bin" not executable?
There are 2 fixes depending on which cause you have: restore the executable bit and reinstall so npm re-links bins. Work through them in order, since the first is the most common.
What does npm package "bin" not executable actually mean?
A CLI installed as a dependency fails with Permission denied when run from node_modules/.bin, or a local package’s own bin cannot execute.
How do I stop npm package "bin" not executable happening again?
Commit the executable bit on your package’s bin files. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card