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
- Open the project's brand.json and inspect the contents to see what the agent actually wrote.
- Re-run the extraction so the agent writes a clean JSON file.
- 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
- Instruct the extraction agent to write raw JSON (or a clearly fenced ```json block) and nothing else.
- Detect partial writes (file size growing) and wait before finalizing.
- Keep the lenient fence-extractor path in mind — wrapping output in ```json lets it recover commentary-wrapped JSON.
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
- brand.json not found in the extraction project — the agent h
- brand.json failed validation: ${errorMessage(err)}
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
- proposal patch.after is not valid JSON
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/88b41c4dd85c57fa.
Report an issue: GitHub.