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
- Create the executable file at the expected location (e.g. `<bin-dir>/<program>-<subcommand>.js`).
- 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(()=>{})`.
- Point to the right file explicitly: `.command('pull', 'desc', { executableFile: path.join(__dirname, 'pull.js') })`.
- 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
- Prefer the action-handler form (.command('x').action(...)) unless you need a separate process.
- If using executable form, set executableDir(__dirname) and verify the file ships (check package.json `files`).
- Add a CI check that every declared executable subcommand file exists in the build output.
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
- Not a number.
- Not a number.
- Allowed choices are ${this.argChoices.join(', ')}.
- '${executableFile}' not executable
- Command passed to .addCommand() must have a name - specify t
AI-assisted analysis of tj/commander.js@ba6d13ddb4 (2026-08-03).
Data as JSON: /data/errors/66be94356bd0c3f3.json.
Report an issue: GitHub.