pbakaus/impeccable · error

concept-seed: no local catalog (set IMPECCABLE_CATALOG_DIR o

Error message

concept-seed: no local catalog (set IMPECCABLE_CATALOG_DIR or pass sourceConcepts)

What it means

Thrown by requireLocalConcepts() when loadLocal() returned null — meaning no usable local catalog was found at IMPECCABLE_CATALOG_DIR (directory missing, files missing, or catalog validation failed and was swallowed). The caller asked for the local catalog explicitly, so rather than silently falling through to the roll API it surfaces the requirement.

Source

Thrown at plugin/skills/impeccable/scripts/concept-seed.mjs:157

    const compositionState = readCompositionCatalog(
      join(catalogDir, 'composition-ingredients.json'),
      join(catalogDir, 'composition-reviews.json')
    );
    localState = {
      concepts: catalogState.concepts,
      compositions: compositionState.compositions,
    };
  } catch {
    localState = null;
  }
  localStates.set(catalogDir, localState);
  return localState;
}

function requireLocalConcepts() {
  const local = loadLocal();
  if (!local) {
    throw new Error('concept-seed: no local catalog (set IMPECCABLE_CATALOG_DIR or pass sourceConcepts)');
  }
  return local;
}

async function fetchRoll({ scope, key, mode, grain, platform, reroll }) {
  const params = new URLSearchParams({ scope, key, reroll: String(reroll) });
  if (mode) params.set('mode', mode);
  if (grain) params.set('grain', grain);
  if (platform) params.set('platform', platform);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), apiBudgetMs());
  try {
    // Race the budget explicitly: abort signals do not reliably cancel the
    // TCP connect phase, so a blackholed route would otherwise stall ~10s.
    const response = await Promise.race([
      fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }),
      new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())),
    ]);

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Set IMPECCABLE_CATALOG_DIR to a directory holding valid concept-ingredients.json / concept-reviews.json (and the composition equivalents).
  2. If you do not have the private catalog, pass sourceConcepts explicitly or use a mode that falls through to the roll API instead of requiring local.
  3. If the directory is set but still null, validate the catalog JSON (see error [3]) to find the schema violation.

Example fix

# before
$ node concept-seed.mjs --sourceConcepts local ...
# throws: no local catalog

# after
$ IMPECCABLE_CATALOG_DIR=/path/to/catalog node concept-seed.mjs --sourceConcepts local ...
Defensive patterns

Strategy: validation

Validate before calling

const local = loadLocal(process.env.IMPECCABLE_CATALOG_DIR || CATALOG_DIR);
if (!local) {
  throw new Error('Set IMPECCABLE_CATALOG_DIR to a valid catalog directory before using local concepts.');
}

Type guard

function localCatalogAvailable(catalogDir) {
  return loadLocal(catalogDir) !== null;
}

Try / catch

try {
  requireLocalConcepts();
} catch (err) {
  if (/no local catalog/.test(err.message)) {
    console.error('No local catalog; set IMPECCABLE_CATALOG_DIR or use a non-local source.');
  } else throw err;
}

Prevention

When it happens

Trigger: Calling a code path that uses requireLocalConcepts() (e.g. --sourceConcepts=local or a mode that must read the private catalog) when IMPECCABLE_CATALOG_DIR is unset, points at a non-existent directory, or holds catalog JSON that failed validation (see error [3]).

Common situations: Running the seeder outside the private service repo without IMPECCABLE_CATALOG_DIR set; the catalog directory was moved; a catalog schema change made the local files invalid.

Related errors


AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13). Data as JSON: /api/errors/b5319bfe187e315f. Report an issue: GitHub.