CherryHQ/cherry-studio · error · Error

[theme-contract] ${mode} ${declaration.name} in ${declaratio

Error message

[theme-contract] ${mode} ${declaration.name} in ${declaration.source} references undefined ${reference}

What it means

Thrown by assertReferencesResolve when a `var(--x)` reference inside a declaration's value does not match any declared variable in the resolved mode graph (light = :root only; dark = :root merged with .dark overrides). Every var() reference must point at a variable declared in the same mode. The message gives the mode, the referencing variable, its source file, and the unresolved reference.

Source

Thrown at packages/ui/scripts/validate-theme-contract.ts:187

function assertCompatibilityTokensDeclared(
  label: string,
  tokenNames: readonly string[],
  source: string,
  sourceName: string
): void {
  const declarations = new Set(extractDeclarations(source, sourceName).map((declaration) => declaration.name))
  const missing = tokenNames.map((token) => `--cs-${token}`).filter((name) => !declarations.has(name))

  if (missing.length > 0) {
    throw new Error(`[theme-contract] ${label} references missing foundation variables: ${missing.join(', ')}`)
  }
}

function assertReferencesResolve(mode: string, declarations: Map<string, Declaration>): void {
  for (const declaration of declarations.values()) {
    for (const reference of extractReferences(declaration.value, declaration.source)) {
      if (!declarations.has(reference)) {
        throw new Error(
          `[theme-contract] ${mode} ${declaration.name} in ${declaration.source} references undefined ${reference}`
        )
      }
    }
  }
}

function assertNoCycles(mode: string, declarations: Map<string, Declaration>): void {
  const visited = new Set<string>()
  const visiting = new Set<string>()
  const stack: string[] = []

  const visit = (name: string): void => {
    if (visited.has(name)) return
    if (visiting.has(name)) {
      const cycleStart = stack.indexOf(name)
      throw new Error(`[theme-contract] ${mode} variable cycle: ${[...stack.slice(cycleStart), name].join(' -> ')}`)
    }

View on GitHub (pinned to 726446b54c)

Solutions

  1. Check the spelling of the unresolved reference in the named source file and fix it to match an existing declaration.
  2. If the name is correct, declare the missing variable in the right layer/mode (foundation --cs-* for foundation refs, etc.), or restore it if it was deleted.
  3. If the reference is obsolete, remove the var() and replace with a concrete value or a valid reference.

Example fix

// before (providers.css)
--cs-primary: var( --cs-missing-primary);
// after
--cs-primary: var(--cs-brand-500);
Defensive patterns

Strategy: validation

Validate before calling

// Pre-resolve every var() reference against the union of declarations in the mode.
function unresolvedRefs(source: string, known: Set<string>) {
  const out: string[] = []
  for (const [, name] of source.matchAll(/var\(\s*(--[a-z0-9-]+)/g)) {
    if (!known.has(name)) out.push(name)
  }
  return out
}

Try / catch

try {
  validateThemeContractSources(sources)
} catch (error) {
  if (error instanceof Error && /references undefined/.test(error.message)) {
    console.error(error.message) // names the referencing var, source, and undefined ref
    process.exitCode = 1
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Typing `var(--cs-primay)` instead of `var(--cs-primary)`; referencing a variable that is only declared inside `.dark` from a `:root` declaration; deleting a foundation variable that other layers still reference; writing `var( --cs-x )` with leading whitespace (the extractor handles this and still flags a genuinely missing target).

Common situations: Renaming a token in one file without updating its consumers; splitting a variable between light/dark and forgetting the light declaration; rebasing onto a refactor that renamed primitives.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/9e55e2ce5a705ab9. Report an issue: GitHub.