# How to Debug Why a Token Reference Isn't Resolving in the Design MD CLI

> Debug unresolved token references in Design MD CLI. Verify syntax, inspect symbolTable, and trace resolveReference for issues like missing keys or circular references.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-06-28

---

**To debug an unresolved token reference in the Design MD CLI, verify the syntax with `isTokenReference`, inspect the `symbolTable` for the target key, and trace the `resolveReference` function in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts) to detect missing keys, circular references, or depth limit violations.**

The Design MD CLI from the [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) repository processes design-token references like `{colors.brand}` through a multi-phase resolution pipeline. When a token reference isn't resolving, the issue typically stems from syntax errors, missing symbol table entries, or circular dependencies in the token chain. Understanding how the CLI resolves these references allows you to pinpoint exactly where the resolution breaks down.

## Understanding the Three-Phase Resolution Pipeline

The CLI resolves design-token references in three distinct phases:

1. **Primitive token parsing** – Raw values are stored in a `symbolTable` alongside any already-resolved values.
2. **Chained reference resolution** – The CLI walks the symbol table, replacing token-reference strings with concrete values using the `resolveReference` function (lines 73-98 in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts)).
3. **Component construction** – Component properties are populated with resolved values, and any reference that could not be resolved is recorded in the `unresolvedRefs` array (lines 84-101).

## Common Root Causes of Unresolved Token References

When `resolveReference` returns `null`, it typically indicates one of these five issues:

**Invalid token syntax** – If `isTokenReference` returns `false`, the string lacks proper curly braces `{}` and is stored unchanged in the symbol table, bypassing resolution entirely.

**Non-existent target entry** – When `symbolTable.get(path)` returns `null` inside `resolveReference`, the reference path does not exist in the symbol table, causing the token to be added to `unresolvedRefs`.

**Circular reference** – The cycle detection mechanism (`visited.has(path)`) identifies when a token references itself through a chain, returning `null` and leaving the original reference unresolved.

**Depth limit exceeded** – The `MAX_REFERENCE_DEPTH` guard (defined in [`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts) at line 31) aborts resolution when chained references exceed the default limit of 20 levels.

**Type mismatch** – After resolution, the caller verifies `resolved.type` (e.g., lines 133-137 for color validation). If the resolved token type does not match the expected property type, the value is ignored and the reference remains unresolved.

## Step-by-Step Debugging Guide

Follow these steps to isolate why a specific token reference is failing:

1. **Confirm the token syntax**

Verify that your token uses proper curly-brace notation:

```typescript
import { isTokenReference } from './packages/cli/src/linter/model/spec.js';

const token = '{colors.brand}';
console.log(isTokenReference(token)); // Must return true

```

If `isTokenReference` returns `false`, the CLI will never attempt resolution.

2. **Inspect the symbol table after Phase 1**

Insert a temporary debug statement after the primitive parsing loops (around line 74 in [`handler.ts`](https://github.com/google-labs-code/design.md/blob/main/handler.ts)):

```typescript
console.log('Symbol Table:', symbolTable);

```

Look for entries like `colors.brand → '{colors.base}'` (indicating a chained reference) or a concrete value.

3. **Check that the target key exists**

Extract the path and verify its presence:

```typescript
const path = token.slice(1, -1); // "colors.brand"
console.log(symbolTable.has(path)); // Must return true

```

4. **Run the resolver manually**

Test the resolution logic directly:

```typescript
import { resolveReference } from './packages/cli/src/linter/model/handler.js';

const resolved = resolveReference(symbolTable, 'colors.brand', new Set());
console.log('Resolved:', resolved);

```

If `resolved` is `null`, the key is missing, a cycle was detected, or the depth limit was exceeded.

5. **Detect cycles or deep chains**

Monitor the `visited` Set while stepping through `resolveReference`. For depth issues, check if your chain exceeds `MAX_REFERENCE_DEPTH` (20 levels by default). Increase this constant in [`spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/spec-config.ts) only after confirming the design intent.

6. **Verify the final type**

Ensure the resolved token type matches the consumer's expectation. If you expect a color but receive a dimension, the handler will skip the value (see lines 133-137 in [`handler.ts`](https://github.com/google-labs-code/design.md/blob/main/handler.ts)).

7. **Read the unresolvedRefs array**

After model construction, inspect the `unresolvedRefs` array populated at line 98:

```typescript
console.log('Unresolved:', component.unresolvedRefs);

```

This array contains all references that survived the resolution pipeline.

8. **Run the unit tests**

Execute the test suite in [`packages/cli/src/linter/model/handler.test.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.test.ts) (specifically lines 225-282) to verify resolution behavior against known examples:

```bash
npm test

```

## Typical Pitfalls to Avoid

Watch for these common mistakes that prevent token resolution:

- **Whitespace or extra characters** – The `isTokenReference` function requires exact `{...}` syntax without surrounding spaces.
- **Case-sensitivity** – Keys are case-sensitive; `colors.Brand` does not match `colors.brand`.
- **Missing section prefix** – References must include the full path including section prefixes like `colors.` or `spacing.`.

## Quick Sanity Check Script

Run this standalone script inside the repository root to verify token resolution:

```typescript
import { readFileSync } from 'fs';
import { isTokenReference } from './packages/cli/src/linter/model/spec.js';
import { resolveReference } from './packages/cli/src/linter/model/handler.js';

// Assuming symbolTable is built from your DESIGN.md or token JSON
const token = '{colors.brand}';

if (!isTokenReference(token)) {
  console.error('Token syntax invalid');
} else {
  const path = token.slice(1, -1);
  const resolved = resolveReference(symbolTable, path, new Set());
  console.log('Resolved value:', resolved);
}

```

## Key Source Files for Reference

Understanding these files accelerates debugging:

- **[`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts)** – Contains the `resolveReference` function and `unresolvedRefs` logic.
- **[`packages/cli/src/linter/model/spec.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/spec.ts)** – Defines `isTokenReference` and validation helpers.
- **[`packages/cli/src/linter/model/spec.test.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/spec.test.ts)** – Tests for token reference detection.
- **[`packages/cli/src/linter/model/handler.test.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.test.ts)** – Comprehensive tests for direct, chained, and circular references (lines 225-282).
- **[`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts)** – Holds `MAX_REFERENCE_DEPTH` and resolution limits.

## Summary

- The Design MD CLI resolves tokens through primitive parsing, chained resolution via `resolveReference`, and component construction phases.
- Unresolved references typically result from invalid syntax, missing symbol table entries, circular dependencies, depth limit violations, or type mismatches.
- Debug by verifying `isTokenReference` returns true, checking `symbolTable.has(path)`, and tracing `resolveReference` for null returns.
- Inspect `unresolvedRefs` on components and consult [`handler.test.ts`](https://github.com/google-labs-code/design.md/blob/main/handler.test.ts) for expected resolution behavior.
- Reference [`spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/spec-config.ts) to adjust `MAX_REFERENCE_DEPTH` when dealing with deeply nested token chains.

## Frequently Asked Questions

### Why does my token reference remain as plain text in the output?

If a token appears as literal text like `{colors.brand}` instead of resolving to a value, `isTokenReference` likely returned `false` due to malformed syntax or extra whitespace. The CLI stores the string unchanged in the symbol table when it fails the curly-brace validation check defined in [`packages/cli/src/linter/model/spec.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/spec.ts).

### How do I fix a circular reference error?

Circular references occur when token A references token B, which eventually references token A again. The `resolveReference` function detects this using a `visited` Set and returns `null`. Break the cycle by editing your DESIGN.md file to remove the recursive dependency, ensuring tokens form a directed acyclic graph.

### Can I increase the maximum depth for chained token references?

Yes. The default limit of 20 chained references is defined as `MAX_REFERENCE_DEPTH` in [`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts) at line 31. Increase this constant only after confirming that your design system intentionally requires deeper nesting, as excessive chaining can indicate architectural issues.

### Where does the CLI report which references failed to resolve?

The CLI populates the `unresolvedRefs` array on each component during the construction phase (lines 84-101 in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts)). Inspect this array in your component output or test results to see exactly which token paths could not be resolved.