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
- Check the spelling of the unresolved reference in the named source file and fix it to match an existing declaration.
- 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.
- 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
- After renaming or deleting a token, grep packages/ui/src/styles for var(<old-name>) and update consumers.
- Declare a variable in :root before referencing it from :root; only dark-only values may live solely in .dark.
- Run theme:check before commit so the reference resolver flags stragglers.
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
- [theme-contract] ${declaration.name} is defined twice in ${s
- [theme-contract] ${label} is missing root declarations: ${mi
- [theme-contract] ${label} references missing foundation vari
- [theme-contract] ${mode} variable cycle: ${[...stack.slice(c
- [theme-contract] foundation cannot declare runtime input ${d
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/9e55e2ce5a705ab9.
Report an issue: GitHub.