affaan-m/ECC · error · Error

Workers must have unique slugs — duplicate

Error message

Workers must have unique slugs — duplicate: ${workerSlug}

What it means

buildOrchestrationPlan validates that every worker in an orchestration plan maps to a distinct slug (derived via slugify from worker.name or a positional fallback). If two workers slugify to the same value, later resources (branch, worktree path, coordination dir) would collide, so the plan is rejected up front with this error.

Solutions

  1. Rename the duplicate workers so each has a unique name in the orchestration config.
  2. If workers are generated programmatically, append a unique suffix (e.g. index) to each name.
  3. Check slugify behavior: names differing only in case, punctuation, or whitespace can collapse to the same slug — make names differ alphanumerically.

Example fix

// before
workers: [
  { name: 'Build' },
  { name: 'Build' }
]
// after
workers: [
  { name: 'Build' },
  { name: 'Test' }
]
Defensive patterns

Strategy: validation

Validate before calling

const names = workers.map(w => w.name);
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
if (dupes.length) throw new Error(`duplicate worker names: ${[...new Set(dupes)].join(', ')}`);
const slugs = names.map(n => n.toLowerCase().replace(/[^a-z0-9]+/g, '-'));
if (new Set(slugs).size !== slugs.length) throw new Error('workers collapse to duplicate slugs');

Try / catch

try {
  const plan = buildOrchestrationPlan(config);
} catch (err) {
  if (String(err.message).includes('unique slugs')) {
    console.error('Fix worker names in config — duplicates found:', err.message);
    process.exitCode = 1;
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling plan() (which calls buildOrchestrationPlan) with a config where two workers share the same name, or where names differ only by characters that slugify strips/normalizes (e.g. 'Worker 1' and 'worker-1', or 'My Agent!' and 'my-agent').

Common situations: Copy-pasting a worker entry in the orchestration config and forgetting to rename it; renaming workers to names that collapse to the same slug after normalization; generating workers programmatically without unique names.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/a6628fa9029f9e72. Report an issue: GitHub.

Appendix: source

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

  const coordinationDir = path.join(coordinationRoot, sessionName);
  const baseRef = config.baseRef || 'HEAD';
  const defaultLauncher = config.launcherCommand || '';

  if (workers.length === 0) {
    throw new Error('buildOrchestrationPlan requires at least one worker');
  }

  const seenSlugs = new Set();
  const workerPlans = workers.map((worker, index) => {
    if (!worker || typeof worker.task !== 'string' || worker.task.trim().length === 0) {
      throw new Error(`Worker ${index + 1} is missing a task`);
    }

    const workerName = worker.name || `worker-${index + 1}`;
    const workerSlug = slugify(workerName, `worker-${index + 1}`);

    if (seenSlugs.has(workerSlug)) {
      throw new Error(`Workers must have unique slugs — duplicate: ${workerSlug}`);
    }
    seenSlugs.add(workerSlug);

    const branchName = `orchestrator-${sessionName}-${workerSlug}`;
    const worktreePath = path.join(worktreeRoot, `${repoName}-${sessionName}-${workerSlug}`);
    const workerCoordinationDir = path.join(coordinationDir, workerSlug);
    const taskFilePath = path.join(workerCoordinationDir, 'task.md');
    const handoffFilePath = path.join(workerCoordinationDir, 'handoff.md');
    const statusFilePath = path.join(workerCoordinationDir, 'status.md');
    const launcherCommand = worker.launcherCommand || defaultLauncher;
    const workerSeedPaths = normalizeSeedPaths(worker.seedPaths, repoRoot);
    const seedPaths = normalizeSeedPaths([...globalSeedPaths, ...workerSeedPaths], repoRoot);
    const templateVariables = buildTemplateVariables({
      branch_name: branchName,
      handoff_file: handoffFilePath,
      repo_root: repoRoot,
      session_name: sessionName,
      status_file: statusFilePath,

View on GitHub (pinned to 8321021c54)