tj/commander.js · critical · Error

'${executableFile}' does not exist - if '${subcommandName}'

Error message

'${executableFile}' does not exist
 - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
 - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
 - ${executableDirMessage}

What it means

Thrown by Command._checkForMissingExecutable() at lib/command.js:1196-1207 when an executable subcommand (created via `.command('name', 'description', opts)` style) is invoked but the expected executable file cannot be found on disk. Commander searches executableDir (default: directory of the running script, or set via .executableDir()) for a file named `<program>-<subcommand>` with one of the supported extensions (.js/.ts/.tsx/.mjs/.cjs) or an explicit executableFile path.

Source

Thrown at lib/command.js:1206

  /**
   * Throw if expected executable is missing. Add lots of help for author.
   *
   * @param {string} executableFile
   * @param {string} executableDir
   * @param {string} subcommandName
   */
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
    if (fs.existsSync(executableFile)) return;

    const executableDirMessage = executableDir
      ? `searched for local subcommand relative to directory '${executableDir}'`
      : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
    const executableMissing = `'${executableFile}' does not exist
 - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
 - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
 - ${executableDirMessage}`;
    throw new Error(executableMissing);
  }

  /**
   * Execute a sub-command executable.
   *
   * @private
   */

  _executeSubCommand(subcommand, args) {
    args = args.slice();
    const sourceExt = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];

    function findFile(baseDir, baseName) {
      // Look for specified file
      const localBin = path.resolve(baseDir, baseName);
      if (fs.existsSync(localBin)) return localBin;

      // Stop looking if candidate already has an expected extension.

View on GitHub (pinned to ba6d13ddb4)

Solutions

  1. Create the executable file at the expected location (e.g. `<bin-dir>/<program>-<subcommand>.js`).
  2. If you did NOT mean an executable subcommand, switch to the action-handler form: remove the description-as-2nd-arg and use `.command('name').description('...').action(()=>{})`.
  3. Point to the right file explicitly: `.command('pull', 'desc', { executableFile: path.join(__dirname, 'pull.js') })`.
  4. Set the search directory: `program.executableDir(__dirname)`.

Example fix

// before (executable form, file missing)
program.command('pull', 'fetch from remote');

// after (action handler, no external file needed)
program.command('pull').description('fetch from remote').action(() => { /* ... */ });
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import path from 'node:path';
function assertExecutableExists(file) {
  if (!fs.existsSync(file)) {
    throw new Error(`Missing executable subcommand file: ${file}`);
  }
}
// or at config time, prefer the action-handler form unless you truly need a separate process.

Try / catch

try { await program.parseAsync(); }
catch (e) {
  if (/does not exist/.test(e.message)) {
    console.error('Subcommand executable missing. Did you mean the action-handler form?');
    process.exit(127);
  }
  throw e;
}

Prevention

When it happens

Trigger: Declaring `program.command('pull', 'fetch from remote')` (the description-as-2nd-arg form marks it executable) but never creating the `mycli-pull.js` file; running from a different cwd so the relative search misses; packaging that omitted the subcommand script; a typo'd executableFile option.

Common situations: Monorepo where the bin script lives in a different package than the subcommand; publishing to npm without including the subcommand files (files/engorge field); refactor that moved scripts but not the executableDir; dev vs packaged path differences.

Related errors


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