pbakaus/impeccable · error · 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 valid local concept catalog could be loaded. The skill resolves concepts from a local catalog first (the private service repo, evals, and tests point IMPECCABLE_CATALOG_DIR at one), then the roll API, then a degraded assignment-only seed — but requireLocalConcepts demands the local path. The full catalog intentionally does not ship with the skill.

Source

Thrown at skill/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 containing valid concept-ingredients.json + concept-reviews.json.
  2. If you are calling seedConcepts programmatically, pass sourceConcepts directly to bypass the local-catalog requirement.
  3. Verify the catalog files exist and pass validation (validateConceptCatalog) at that path.
  4. If you only have API access, use the fetchRoll path instead of requireLocalConcepts.

Example fix

// before
process.env.IMPECCABLE_CATALOG_DIR; // undefined -> throws [83]
const local = requireLocalConcepts();

// after: point at a real catalog, or supply concepts inline
process.env.IMPECCABLE_CATALOG_DIR = '/path/to/private/catalog';
// or, programmatically:
const seeded = await seedConcepts({ sourceConcepts: myConcepts, scope: 'surface' });
Defensive patterns

Strategy: validation

Validate before calling

// Verify a usable local catalog exists before calling requireLocalConcepts.
import { existsSync } from 'node:fs';
import { join } from 'node:path';
function hasLocalCatalog(dir = process.env.IMPECCABLE_CATALOG_DIR) {
  if (!dir) return false;
  return existsSync(join(dir, 'concept-ingredients.json'))
    && existsSync(join(dir, 'concept-reviews.json'));
}

Prevention

When it happens

Trigger: IMPECCABLE_CATALOG_DIR is unset and the script directory has no catalog files; the env var points at an empty or non-existent directory; the catalog files exist but failed validation (see [82]); a fresh public checkout where the private catalog is absent.

Common situations: Running concept-seed in a plain skill install (no private catalog); a CI job that forgot to set IMPECCABLE_CATALOG_DIR; pointing the env var at a stale checkout; catalog JSON corrupted after a bad merge.

Related errors


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