tj/commander.js · critical · Error

'${executableFile}' not executable

Error message

'${executableFile}' not executable

What it means

Thrown by the child_process error handler in Command._executeSubCommand() at lib/command.js:1339-1342 when spawning an executable subcommand fails with EACCES — the resolved executable file exists but lacks the executable bit (chmod +x). Distinct from ENOENT (missing file, see error[16]): here the file is found but the OS refuses to run it.

Source

Thrown at lib/command.js:1341

          new CommanderError(
            code,
            'commander.executeSubCommandAsync',
            '(close)',
          ),
        );
      }
    });
    proc.on('error', (err) => {
      // @ts-ignore: because err.code is an unknown property
      if (err.code === 'ENOENT') {
        this._checkForMissingExecutable(
          executableFile,
          executableDir,
          subcommand._name,
        );
        // @ts-ignore: because err.code is an unknown property
      } else if (err.code === 'EACCES') {
        throw new Error(`'${executableFile}' not executable`);
      }
      if (!exitCallback) {
        process.exit(1);
      } else {
        const wrappedError = new CommanderError(
          1,
          'commander.executeSubCommandAsync',
          '(error)',
        );
        wrappedError.nestedError = err;
        exitCallback(wrappedError);
      }
    });

    // Store the reference to the child process
    this.runningCommand = proc;
  }

View on GitHub (pinned to ba6d13ddb4)

Solutions

  1. Set the executable bit: `chmod +x <file>` (e.g. `chmod +x bin/mycli-pull.js`).
  2. Ensure your shebang (`#!/usr/bin/env node`) is present so the OS knows how to run it once executable.
  3. If deploying via npm, declare the file in package.json `bin` so npm sets +x on install, and verify the `files` array includes it.
  4. In Docker/CI, copy with `COPY --chmod=0755` or run chmod in the image build.

Example fix

# before: file is -rw-r--r--
#   bin/mycli-pull.js

# after
chmod +x bin/mycli-pull.js
# ensure shebang on first line:
#!/usr/bin/env node
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
function assertExecutable(file) {
  if (!fs.existsSync(file)) throw new Error(`missing: ${file}`);
  const st = fs.statSync(file);
  if (!(st.mode & 0o111)) throw new Error(`${file} is not executable (run chmod +x)`);
}

Try / catch

try { await program.parseAsync(); }
catch (e) {
  if (/not executable/i.test(e.message)) {
    console.error('Run: chmod +x <subcommand-file>');
    process.exit(126);
  }
  throw e;
}

Prevention

When it happens

Trigger: An executable subcommand file exists but has mode 0644 (no execute permission). Common after `git clone` on Windows where the mode bit was lost, or when files were created via a tool that didn't preserve +x. The spawn raises EACCES and the handler re-throws.

Common situations: Cross-platform development (Windows doesn't track the bit; deploy to Linux loses it); files written by a bundler that resets permissions; npm packaging that strips the bit; container images copied with non-preserving cp.

Related errors


AI-assisted analysis of tj/commander.js@ba6d13ddb4 (2026-08-03). Data as JSON: /data/errors/09867dea11a4f6c7.json. Report an issue: GitHub.