affaan-m/ECC · error
launcherCommand must be a non-empty string
Error message
launcherCommand must be a non-empty string
What it means
Thrown by renderTemplate() in the tmux-worktree-orchestrator when the launcherCommand template (after trimming) is not a non-empty string. The orchestrator builds the per-worker launch command by interpolating variables into this template, so an empty/missing template means there is nothing to send to the tmux pane. The message is intentionally specific to the launcherCommand field rather than generic.
Source
Thrown at scripts/lib/tmux-worktree-orchestrator.js:18
'use strict';
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
function slugify(value, fallback = 'worker') {
const normalized = String(value || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return normalized || fallback;
}
function renderTemplate(template, variables) {
if (typeof template !== 'string' || template.trim().length === 0) {
throw new Error('launcherCommand must be a non-empty string');
}
return template.replace(/\{([a-z_]+)\}/g, (match, key) => {
if (!(key in variables)) {
throw new Error(`Unknown template variable: ${key}`);
}
return String(variables[key]);
});
}
function shellQuote(value) {
return `'${String(value).replace(/'/g, `'\\''`)}'`;
}
function formatCommand(program, args) {
return [program, ...args.map(shellQuote)].join(' ');
}
View on GitHub (pinned to 01e15490f0)
Solutions
- Set config.launcherCommand to a non-empty command, e.g. 'claude --resume {session_name}' or 'codex exec {task_file}'.
- Set worker.launcherCommand on each worker object if you do not want a global default.
- Validate the config upstream: if (!config.launcherCommand?.trim() && !workers.every(w => w.launcherCommand?.trim())) throw new Error('missing launcher');
- Use one of the documented template variables ({task_file}, {session_name}, {worktree_path}, etc.) so the rendered command is meaningful.
Example fix
// before
buildOrchestrationPlan({ repoRoot, workers: [{ task: 'fix bug' }] });
// -> launcherCommand must be a non-empty string
// after
buildOrchestrationPlan({
repoRoot,
launcherCommand: 'claude "{task_file}"',
workers: [{ task: 'fix bug' }],
}); Defensive patterns
Strategy: validation
Validate before calling
function resolveLauncher(config, worker) {
const cmd = (worker?.launcherCommand || config?.launcherCommand || '').trim();
return cmd.length > 0 ? cmd : null;
}
const launcher = resolveLauncher(config, worker);
if (!launcher) {
throw new Error('No launcherCommand configured for worker ' + (worker?.name || '?'));
} Type guard
function isNonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0;
} Try / catch
try {
buildOrchestrationPlan(config);
} catch (error) {
if (/launcherCommand must be a non-empty string/.test(error.message)) {
config.launcherCommand = DEFAULT_LAUNCHER;
buildOrchestrationPlan(config);
return;
}
throw error;
} Prevention
- Always set config.launcherCommand unless every worker sets its own.
- Validate the config object with a schema before passing it to buildOrchestrationPlan.
- Treat empty launcherCommand as a hard configuration error at load time.
- Document a canonical launcher string so users have a copy-pasteable starting point.
When it happens
Trigger: buildOrchestrationPlan is called with config.launcherCommand = '' and a worker whose own launcherCommand is also empty or whitespace-only; launcherCommand is undefined but coerced to '' via the || '' fallback at line 187; renderTemplate is called directly with a non-string argument.
Common situations: A config file omits launcherCommand expecting a default to exist (there is none — empty string is the default); a CLI flag --launcher is provided but empty; a YAML config has launcherCommand: with no value (parsed as empty string); the orchestrator is driven programmatically without setting the global default.
Related errors
- buildOrchestrationPlan requires at least one worker
- Unknown template variable: ${key}
- Seed path does not exist in repoRoot: ${seedPath}
- Worker ${index + 1} is missing a task
- Worker ${workerName} is missing a launcherCommand
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/6987e450e24f5170.
Report an issue: GitHub.