affaan-m/ECC · error · Error

Harness capability root is out of sync for target ${declared

Error message

Harness capability root is out of sync for target ${declaredScope.targetId}

What it means

The third require-time validation: every harness scope declares a display root (like './.cursor' or '~/.config/opencode'), and validateCatalog recomputes the expected root from the scope's adapter — home adapters get a '~/...' prefix, project adapters get './...' plus the OS-normalized relative path — and requires an exact string match. A moved directory, changed prefix, or Windows path separator produces this mismatch at module load.

Source

Thrown at scripts/lib/harness-capabilities.js:281

  if (new Set(catalogTargetIds).size !== catalogTargetIds.length) {
    throw new Error('Harness capability catalog contains duplicate install target ids');
  }

  const supported = [...SUPPORTED_INSTALL_TARGETS].sort();
  const registered = adapters.map(adapter => adapter.target).sort();
  const catalogued = [...catalogTargetIds].sort();
  if (
    JSON.stringify(catalogued) !== JSON.stringify(supported)
    || JSON.stringify(catalogued) !== JSON.stringify(registered)
  ) {
    throw new Error('Harness capability catalog is out of sync with install targets');
  }

  for (const harness of HARNESS_CAPABILITIES) {
    for (const declaredScope of harness.scopes) {
      const adapter = adapterByTarget.get(declaredScope.targetId);
      if (!adapter || expectedRootForAdapter(adapter) !== declaredScope.root) {
        throw new Error(
          `Harness capability root is out of sync for target ${declaredScope.targetId}`
        );
      }
    }
  }
}

validateCatalog();

function listHarnessCapabilities() {
  return HARNESS_CAPABILITIES.slice();
}

function listGuidedHarnesses() {
  return GUIDED_HARNESS_IDS.map(id => LOOKUP.get(id));
}

function getHarnessCapability(value) {

View on GitHub (pinned to d8409a4b08)

Solutions

  1. Recompute the correct value: for the offending targetId, run the same logic as expectedRootForAdapter (prefix from adapter.kind, then path.relative(baseRoot, adapter.resolveRoot(...)) with backslashes converted to '/')
  2. Paste that exact string into the harness's scopes[].root declaration
  3. Keep roots computed rather than hand-maintained where possible so they cannot drift
  4. Re-require the module to confirm the whole validateCatalog() chain passes

Example fix

// before
{ targetId: 'cursor', root: '~/.cursor' }   // adapter is project-scoped

// after
{ targetId: 'cursor', root: './.cursor' }
Defensive patterns

Strategy: validation

Validate before calling

// CI guard: declared scope roots must equal recomputed roots
const caps = require('./scripts/lib/harness-capabilities.js');
const { listInstallTargetAdapters } = require('./scripts/lib/install-targets');
// recompute like expectedRootForAdapter does and compare, e.g.:
// prefix = adapter.kind === 'home' ? '~/' : './';
// expected = prefix + path.relative(baseRoot, adapter.resolveRoot(...)).replace(/\\/g, '/');
// assert declaredScope.root === expected for every scope

Prevention

When it happens

Trigger: An adapter's resolved install root changes (directory moved or its kind flips between home and project) while the harness entry's scopes[].root still holds the old literal string; or the root was written with backslashes / wrong prefix.

Common situations: Refactoring an adapter to install into a different folder; porting roots between home-scoped and project-scoped targets; editing the declared root string by hand and introducing a typo; path separator differences when validating on Windows.

Related errors


AI-assisted analysis of affaan-m/ECC@d8409a4b08 (2026-08-26). Data as JSON: /api/errors/56194bb2cc3c0a14. Report an issue: GitHub.