nexu-io/open-design · error · Error

invalid brand id: ${brandId}

Error message

invalid brand id: ${brandId}

What it means

Thrown by syncBrandSystemToUserDesignSystem when resolveBrandFile returns null for the brandId. The brand id must match /^[a-z0-9][a-z0-9-]*$/ and must not contain '..', else the traversal-safe resolver refuses it. This guards the brand -> user-design-system copy path so a stray id can never escape the brands root.

Source

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

  targetPrefix: string,
): Promise<void> {
  for (const file of collectFiles(sourceDir)) {
    const projectPath = toPosixPath(path.join(targetPrefix, file.rel));
    await writeProjectFile(projectsRoot, projectId, projectPath, fs.readFileSync(file.abs), { overwrite: true }, metadata);
  }
}

function syncBrandSystemToUserDesignSystem(
  userDesignSystemsRoot: string,
  designSystemId: string,
  brandsRoot: string,
  brandId: string,
  designMd: string,
): void {
  const dir = userDesignSystemDir(userDesignSystemsRoot, designSystemId);
  if (!dir) throw new Error(`invalid design system id: ${designSystemId}`);
  const brandRoot = resolveBrandFile(brandsRoot, brandId, []);
  if (!brandRoot) throw new Error(`invalid brand id: ${brandId}`);

  fs.writeFileSync(path.join(dir, 'DESIGN.md'), designMd, 'utf8');
  copyDirectorySync(brandSystemDir(brandsRoot, brandId), path.join(dir, 'system'));
  copyOptionalDirectorySync(path.join(brandRoot, 'logos'), path.join(dir, 'logos'));
  copyOptionalDirectorySync(path.join(brandRoot, 'fonts'), path.join(dir, 'fonts'));
  copyOptionalDirectorySync(path.join(brandRoot, 'imagery'), path.join(dir, 'imagery'));
  copyOptionalDirectorySync(path.join(brandRoot, 'prefetch'), path.join(dir, 'prefetch'));
  const brandJson = resolveBrandFile(brandsRoot, brandId, ['brand.json']);
  if (brandJson && isFile(brandJson)) {
    fs.copyFileSync(brandJson, path.join(dir, 'brand.json'));
  }
}

function userDesignSystemDir(root: string, id: string): string | null {
  if (!id.startsWith('user:')) return null;
  const dirId = id.slice('user:'.length);
  if (!/^[a-z0-9][a-z0-9-]*$/u.test(dirId)) return null;
  const base = path.resolve(root);

View on GitHub (pinned to 5be4028344)

Solutions

  1. Resolve the brand id from listBrandIds(brandsRoot) or newBrandId(sourceUrl) instead of constructing it by hand.
  2. Validate the id with isValidBrandId (exported from store.ts) before calling the sync route, and 404 the client on failure.
  3. Confirm the brand dir actually exists (readBrand(brandsRoot, id) is non-null) before invoking sync.
  4. If a stale design-system id is the real issue, regenerate it from the brands list rather than retrying the same string.

Example fix

// before
syncBrandSystemToUserDesignSystem(udsRoot, dsId, brandsRoot, 'My_Brand', md);
// after
import { isValidBrandId } from './store.js';
if (!isValidBrandId(brandId)) throw new HttpError(404, `no such brand: ${brandId}`);
syncBrandSystemToUserDesignSystem(udsRoot, dsId, brandsRoot, brandId, md);
Defensive patterns

Strategy: validation

Validate before calling

import { isValidBrandId, readBrand } from './store.js';
function assertSyncableBrandId(brandsRoot, id) {
  if (!isValidBrandId(id)) throw new Error(`invalid brand id: ${id}`);
  if (!readBrand(brandsRoot, id)) throw new Error(`brand not found: ${id}`);
}

Type guard

import { isValidBrandId } from './store.js';
const isSyncableBrandId = (id: unknown): id is string =>
  typeof id === 'string' && isValidBrandId(id);

Prevention

When it happens

Trigger: A call to the route that copies a built brand system into a user design system (brands/index.ts:1483) with a brandId that fails isValidBrandId, e.g. 'My_Brand', 'UPPER', 'brand.json', '', '../x', or an id with slashes/spaces.

Common situations: Calling the sync endpoint directly with a hand-typed id; passing a brand.json filename instead of the dir id; corrupted/partial extraction that wrote no valid brand dir before sync was triggered; tests that synthesize ids without going through newBrandId().

Related errors


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