nexu-io/open-design · error · Error

brand.json is not valid JSON.

Error message

brand.json is not valid JSON.

What it means

Thrown by finalizeBrand when JSON.parse(brandJsonRaw) failed AND extractJsonBlock(brandJsonRaw) could not find a fenced code block containing JSON. The file exists and has content, but it is neither parseable JSON nor a markdown fence the lenient extractor can recover. Distinct from error 34 (file absent) and error 36 (parses but fails schema).

Source

Thrown at apps/daemon/src/brands/index.ts:1347

  opts: FinalizeBrandOptions,
): Promise<BrandFinalizeResponse> {
  const { id, brandsRoot, projectsRoot } = opts;
  const meta = readMeta(brandsRoot, id);
  if (!meta) throw new Error(`brand not found: ${id}`);
  const projectId = opts.projectId ?? meta.projectId ?? brandProjectId(id);

  const brandJsonRaw = await readProjectTextOrNull(projectsRoot, projectId, 'brand.json');
  if (brandJsonRaw === null) {
    throw new Error(
      'brand.json not found in the extraction project — the agent has not written the design system yet.',
    );
  }
  let parsed: unknown;
  try {
    parsed = JSON.parse(brandJsonRaw);
  } catch {
    const block = extractJsonBlock(brandJsonRaw);
    if (block === null) throw new Error('brand.json is not valid JSON.');
    parsed = block;
  }
  let brand: Brand;
  try {
    brand = validateBrand(parsed, meta.sourceUrl);
  } catch (err) {
    throw new Error(`brand.json failed validation: ${errorMessage(err)}`);
  }

  // Pull the agent's downloaded assets into the brand workspace so the
  // deterministic builder and the design system see them.
  copyProjectDirToBrand(projectsRoot, projectId, brandsRoot, id, 'logos');
  copyProjectDirToBrand(projectsRoot, projectId, brandsRoot, id, 'fonts');
  copyProjectDirToBrand(projectsRoot, projectId, brandsRoot, id, 'imagery');

  const guideMd =
    (await readProjectTextOrNull(projectsRoot, projectId, 'BRAND.md')) ?? brandGuideMd(brand);

View on GitHub (pinned to 5be4028344)

Solutions

  1. Open the project's brand.json and inspect the contents to see what the agent actually wrote.
  2. Re-run the extraction so the agent writes a clean JSON file.
  3. If the content is JSON wrapped in prose, wrap it in a ```json fence so extractJsonBlock can recover it (the lenient path).
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeJsonOrFence(raw: string): boolean {
  const trimmed = raw.trim();
  if (!trimmed) return false;
  try { JSON.parse(trimmed); return true; } catch { /* fall through */ }
  return /```(?:json)?[\s\S]*?```/i.test(trimmed);
}
const raw = await readProjectTextOrNull(projectsRoot, projectId, 'brand.json');
if (raw !== null && !looksLikeJsonOrFence(raw)) {
  throw new Error('brand.json is neither valid JSON nor a fenced JSON block. Re-run the extraction.');
}

Try / catch

try {
  await finalizeBrand(opts);
} catch (err) {
  if (err instanceof Error && err.message === 'brand.json is not valid JSON.') {
    return conflict('brand.json contents are malformed. Inspect the file or re-run the extraction.');
  }
  throw err;
}

Prevention

When it happens

Trigger: The agent wrote prose/markdown without a ```json fence; wrote truncated or partially-streamed JSON; wrote YAML or another format; the file was corrupted on write.

Common situations: Agent interrupted mid-write (partial flush); model returned commentary instead of structured output; concurrent writes or disk-full truncation.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/88b41c4dd85c57fa. Report an issue: GitHub.