packages/desktop/apps/electron/resources/docs/labels.md
Labels are additive tags that can be applied to sessions. Unlike statuses (which are exclusive — one per session), labels are multi-select (many per session). They support hierarchical organization via nested JSON trees.
CLI-first workflow (recommended): Use
craft-agent label ...commands instead of editing JSON directly.
craft-agent label --help- Canonical command reference: craft-cli.md
~/.craft-agent/workspaces/{id}/labels/config.jsonUnlike statuses, regular labels start empty. Users create whatever labels they need. There are no built-in or required regular labels.
Labels are color-only — rendered as colored circles in the UI. No icons or emoji are supported.
Labels form a nested JSON tree. Hierarchy is the structure itself — parent/child relationships are expressed via the children array. Array position determines display order (no order field needed).
Example:
{
"version": 1,
"labels": [
{
"id": "eng",
"name": "Engineering",
"color": "info",
"children": [
{
"id": "frontend",
"name": "Frontend",
"children": [
{ "id": "react", "name": "React", "color": { "light": "#3B82F6", "dark": "#60A5FA" } }
]
},
{ "id": "backend", "name": "Backend" }
]
},
{ "id": "bug", "name": "Bug", "color": "destructive" }
]
}
This renders as a tree in the sidebar:
Engineering
├─ Frontend
│ └─ React
└─ Backend
Bug
Rules:
order field){
"version": 1,
"labels": [
{
"id": "bug",
"name": "Bug",
"color": "destructive"
},
{
"id": "feature",
"name": "Feature",
"color": "accent",
"children": [
{ "id": "ui", "name": "UI", "color": { "light": "#6366F1", "dark": "#818CF8" } },
{ "id": "api", "name": "API", "color": { "light": "#10B981", "dark": "#34D399" } }
]
}
]
}
| Property | Type | Description |
|---|---|---|
id | string | Unique slug, globally unique across tree (e.g., "bug", "frontend"). Lowercase alphanumeric + hyphens. |
name | string | Display name |
color | EntityColor? | Optional color. System color string (e.g., "accent", "info/80") or custom object ({ "light": "#hex", "dark": "#hex" }). Rendered as a colored circle in the UI. |
valueType | 'string' | 'number' | 'date'? | Optional value type hint. Tells UI what input widget to show and agents what format to write. Omit for boolean (presence-only) labels. |
children | LabelConfig[]? | Optional nested child labels. Array position = display order. |
Same as statuses — see statuses documentation for full details on supported formats and common mistakes.
System colors: "accent", "info", "success", "destructive", "foreground" (with optional /opacity 0–100)
Custom colors: { "light": "#EF4444", "dark": "#F87171" } — supports hex, OKLCH, RGB, HSL formats
Sessions store labels as an array of strings. Boolean labels are bare IDs; valued labels use the :: separator:
{
"labels": ["bug", "priority::3", "due::2026-01-30", "linear::https://linear.app/issue/ENG-456"]
}
"bug" — presence-only, no value"priority::3" — ID + value separated by :::: split happens on the first occurrence only (values may contain ::)Values are inferred from the raw string at parse time:
| Type | Format | Example |
|---|---|---|
number | Finite number | "priority::3", "effort::0.5" |
date | ISO date (YYYY-MM-DD) | "due::2026-01-30" |
string | Anything else | "link::https://example.com" |
Inference order: ISO date check → number check → string fallback.
The optional valueType in config is a hint only — the parser always infers from the raw value regardless.
Prefer craft-agent commands:
craft-agent label create --name "Bug" --color "destructive"
craft-agent label create --name "Priority" --color "accent" --value-type number
craft-agent label create --name "Due Date" --color "info" --value-type date
craft-agent label create --name "Project" --color "foreground/60"
craft-agent label create --name "Alpha" --color "info" --parent-id project
craft-agent label create --name "Beta" --color "success" --parent-id project
Use direct JSON edits only for bulk/manual operations where CLI is not sufficient.
When creating or modifying labels, follow these conventions unless the user explicitly requests otherwise:
Always add colors. Every label should have a color for visual identification (rendered as a colored circle).
Use complementary colors within a category. Sibling labels (children of the same parent) should use colors from the same family or hue range, creating a cohesive visual group. For example, a "Backend" group might use greens/teals for its children (API, Database), while "Frontend" uses indigos/blues (React, CSS).
Use semantic colors for semantic meanings:
"destructive" or red tones"accent" or blue/indigo tones"success" or green tones"info" or sky/cyan tones"foreground/60" or gray tonesColor format reminder: Use custom { "light": "#hex", "dark": "#hex" } objects for sub-labels to get precise color control. Reserve system colors ("accent", "info", "destructive", etc.) for top-level parent categories.
IMPORTANT: Always validate after creating or editing labels:
config_validate({ target: "labels" })
This validates:
Auto-label rules automatically scan user messages and apply labels with extracted values. Configure regex patterns on any label to trigger automatic tagging.
Add autoRules to any label in config.json:
{
"id": "linear-issue",
"name": "Linear Issue",
"color": "purple",
"valueType": "string",
"autoRules": [
{
"pattern": "linear\\.app/[\\w-]+/issue/([A-Z]+-\\d+)",
"valueTemplate": "$1",
"description": "Matches Linear issue URLs"
},
{
"pattern": "\\b([A-Z]{2,5}-\\d+)\\b",
"valueTemplate": "$1",
"description": "Matches bare issue keys like CRA-123"
}
]
}
| Property | Type | Description |
|---|---|---|
pattern | string | Required. Regex with capture groups. Uses flags (default: gi). |
flags | string | Regex flags (default: gi — global, case-insensitive). g is always enforced. |
valueTemplate | string | Template using $1, $2 for capture group substitution. If omitted, uses first capture group. |
description | string | Human-readable description of what this rule matches. |
Rules use JavaScript regular expressions with capture groups:
{
"pattern": "github\\.com/([\\w-]+/[\\w-]+)/pull/(\\d+)",
"valueTemplate": "$1#$2",
"description": "Matches GitHub PR URLs"
}
g flag is always enforced, so all occurrences in a message are found$1, $2, etc. are replaced with matched groups in valueTemplateExtracted values are normalized based on the label's valueType:
| valueType | Raw capture | Normalized |
|---|---|---|
string | CRA-123 | CRA-123 (pass-through) |
number | $45,000 | 45000 (strip symbol + commas) |
number | 1.5M | 1500000 (expand suffix) |
number | 50k | 50000 (expand suffix) |
date | 2026-01-30 | 2026-01-30 (pass-through) |
A workspace that auto-tags Linear issues, deadlines, contacts, and budgets:
{
"version": 1,
"labels": [
{
"id": "linear-issue",
"name": "Linear Issue",
"color": "purple",
"valueType": "string",
"autoRules": [
{ "pattern": "linear\\.app/[\\w-]+/issue/([A-Z]+-\\d+)", "valueTemplate": "$1", "description": "Linear URLs" },
{ "pattern": "\\b([A-Z]{2,5}-\\d+)\\b", "valueTemplate": "$1", "description": "Bare issue keys" }
]
},
{
"id": "deadline",
"name": "Deadline",
"color": "orange",
"valueType": "date",
"autoRules": [
{ "pattern": "(\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2})?)", "valueTemplate": "$1", "description": "ISO dates" }
]
},
{
"id": "contact",
"name": "Contact",
"color": "blue",
"valueType": "string",
"autoRules": [
{ "pattern": "([\\w.+-]+@[\\w.-]+\\.[a-zA-Z]{2,})", "valueTemplate": "$1", "description": "Email addresses" }
]
},
{
"id": "budget",
"name": "Budget",
"color": "green",
"valueType": "number",
"autoRules": [
{ "pattern": "\\$([\\d,.]+[kKmMbB]?)", "valueTemplate": "$1", "description": "Dollar amounts" }
]
}
]
}
Labels appear in the left sidebar as a multi-level expandable section:
All Sessions (flat, total count)
Flagged (flat, flagged count)
States (expandable → status sub-items)
Labels (expandable)
├─ Views (expandable → view sub-items)
├─ Engineering (label)
└─ Bug (label)
────────────
Sources (expandable → API/MCP/Local)
Skills (flat)
────────────
Settings (flat)
Clicking a label filters the session list. Clicking a parent label includes sessions tagged with any descendant.
order field needed, array position determines display orderlabels: string[], not a single value:: separator: Simple, flat string storage — no schema changes to session formatvalueType is just a UI hintYYYY-MM-DD — no time component, avoids timezone complexity