affaan-m/ECC · error

Unknown template variable: ${key}

Error message

Unknown template variable: ${key}

What it means

Thrown by renderTemplate() when the launcherCommand template references a {placeholder} that is not present in the variables map. The orchestrator only populates a fixed set of variables per worker (branch_name, handoff_file, repo_root, session_name, status_file, task_file, worker_name, worker_slug, worktree_path, plus their _raw and _sh variants built by buildTemplateVariables), so any other token is treated as a typo.

Source

Thrown at scripts/lib/tmux-worktree-orchestrator.js:23

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(' ');
}

function buildTemplateVariables(values) {
  return Object.entries(values).reduce((accumulator, [key, value]) => {
    const stringValue = String(value);
    const quotedValue = shellQuote(stringValue);

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Use only the supported tokens: branch_name, handoff_file, repo_root, session_name, status_file, task_file, worker_name, worker_slug, worktree_path (plus _raw and _sh suffixes).
  2. Check the regex in renderTemplate: only lowercase letters and underscores inside { } are interpolated, anything else is left as-is and will not trigger this error — but a typo'd lowercase token will.
  3. If you need a custom value, pre-compute it and substitute before passing the template, or extend buildTemplateVariables in scripts/lib/tmux-worktree-orchestrator.js.
  4. Print Object.keys(templateVariables) in a debug run to see exactly what is available.

Example fix

// before
launcherCommand: 'claude --task {task} --cwd {worktree}'
// -> Unknown template variable: task

// after
launcherCommand: 'claude --task-file {task_file} --cwd {worktree_path}'
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = ['branch_name','handoff_file','repo_root','session_name','status_file','task_file','worker_name','worker_slug','worktree_path'];

function validateLauncherTemplate(tpl) {
  const used = [...tpl.matchAll(/\{([a-z_]+)\}/g)].map(m => m[1]);
  const unknown = used.filter(k => !ALLOWED.includes(k) && !ALLOWED.some(a => k === `${a}` || k === `${a}_raw` || k === `${a}_sh`));
  if (unknown.length) throw new Error(`Unknown template tokens: ${unknown.join(', ')}`);
}

validateLauncherTemplate(config.launcherCommand);

Type guard

const ALLOWED = ['branch_name','handoff_file','repo_root','session_name','status_file','task_file','worker_name','worker_slug','worktree_path'];

function usesOnlyKnownTokens(tpl) {
  return ![...tpl.matchAll(/\{([a-z_]+)\}/g)].map(m => m[1])
    .some(k => !ALLOWED.includes(k.replace(/_(raw|sh)$/, '')));
}

Try / catch

try {
  buildOrchestrationPlan(config);
} catch (error) {
  if (/Unknown template variable/.test(error.message)) {
    // surface the list of allowed variables to the user
    throw new Error(`${error.message}. Allowed: ${ALLOWED.join(', ')}`);
  }
  throw error;
}

Prevention

When it happens

Trigger: config.launcherCommand = 'cd {worktree} && claude' (worktree vs worktree_path); using {task} instead of {task_file}; using {repo} instead of {repo_root}; using an uppercase token like {Session_name} (the regex only matches [a-z_]); referencing a variable that exists on a different worker.

Common situations: Documentation drift: an example uses an old variable name; a user invents a placeholder assuming it will be passed through; case-sensitivity issues; a template copied from a different orchestrator.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/a042825c44114fa5. Report an issue: GitHub.