examples/linear-workflow/README.md
Bidirectional synchronization between Linear and bd (beads) using the built-in bd linear commands.
The Linear integration provides:
# Set API key via environment variable (recommended — avoids git exposure)
export LINEAR_API_KEY="lin_api_YOUR_API_KEY_HERE" # add to ~/.secrets or ~/.zshrc
# Set team ID
bd config set linear.team_id "YOUR_TEAM_UUID"
# Check configuration status
bd linear status
# Pull issues from Linear
bd linear sync --pull
# Pull issues and Linear blocking relations as bd dependencies
bd linear sync --pull --relations
# Push local issues to Linear
bd linear sync --push
# Full bidirectional sync (pull, resolve conflicts, push)
bd linear sync
Linear uses Personal API Keys for authentication. Create one at: Linear → Settings → API → Personal API keys
Store securely:
# Recommended: Environment variable (avoids git exposure)
export LINEAR_API_KEY="lin_api_..." # add to ~/.secrets or ~/.zshrc
# Alternative: bd config (only if config.yaml is NOT git-tracked)
bd config set linear.api_key "lin_api_..."
Find your Team ID in Linear:
https://linear.app/YOUR_TEAM/... → Go to team settingsImport issues from Linear without pushing local changes:
bd linear sync --pull
# Import Linear relations as bd dependencies
bd linear sync --pull --relations
# Filter by state
bd linear sync --pull --state open # Only open issues
bd linear sync --pull --state closed # Only closed issues
bd linear sync --pull --state all # All issues (default)
# Reconstruct Linear project milestones as local epic parents
bd linear sync --pull --milestones
With --milestones, bd creates or reuses one local epic per Linear
projectMilestone, then adds parent-child links from each pulled issue to its
milestone epic. Milestone epics are marked as Linear milestone records and are
skipped by later Linear pushes.
Export local issues to Linear without pulling:
bd linear sync --push
# Create only (don't update existing Linear issues)
bd linear sync --push --create-only
# Disable automatic external_ref update
bd linear sync --push --update-refs=false
Full two-way sync with conflict detection and resolution:
# Default: newer timestamp wins conflicts
bd linear sync
# Always prefer local version on conflicts
bd linear sync --prefer-local
# Always prefer Linear version on conflicts
bd linear sync --prefer-linear
Preview what would happen without making changes:
bd linear sync --dry-run
Linear and Beads use different priority semantics:
| Linear | Meaning | Beads | Meaning |
|---|---|---|---|
| 0 | No priority | 4 | Backlog |
| 1 | Urgent | 0 | Critical |
| 2 | High | 1 | High |
| 3 | Medium | 2 | Medium |
| 4 | Low | 3 | Low |
Default mapping (Linear → Beads):
Custom mappings:
# Override default mappings
bd config set linear.priority_map.0 2 # No priority -> Medium (instead of Backlog)
bd config set linear.priority_map.1 1 # Urgent -> High (instead of Critical)
Map Linear workflow states to bd statuses:
| Linear State Type | Beads Status |
|---|---|
| backlog | open |
| unstarted | open |
| started | in_progress |
| completed | closed |
| canceled | closed |
Custom state mappings (for custom workflow states):
# Map by state type
bd config set linear.state_map.started in_progress
# Map by state name (for custom workflow states)
bd config set linear.state_map.in_review in_progress
bd config set linear.state_map.blocked blocked
bd config set linear.state_map.on_hold blocked
bd config set linear.state_map.testing in_progress
bd config set linear.state_map.deployed closed
Infer bd issue type from Linear labels:
| Linear Label | Beads Type |
|---|---|
| bug, defect | bug |
| feature, enhancement | feature |
| epic | epic |
| chore, maintenance | chore |
| task | task |
Custom label mappings:
bd config set linear.label_type_map.incident bug
bd config set linear.label_type_map.improvement feature
bd config set linear.label_type_map.tech_debt chore
bd config set linear.label_type_map.story feature
Map Linear relations to bd dependencies:
Relation import is opt-in during pull:
bd linear sync --pull --relations
| Linear Relation | Beads Dependency |
|---|---|
| blocks | blocks |
| blockedBy | blocks (inverted) |
| duplicate | duplicates |
| related | related |
| (parent) | parent-child |
Custom relation mappings:
bd config set linear.relation_map.causes discovered-from
bd config set linear.relation_map.duplicate related
Conflicts occur when both local and Linear versions are modified since the last sync.
The newer version wins:
bd linear sync # Newer timestamp wins
Local bd version always wins:
bd linear sync --prefer-local
Use when:
Linear version always wins:
bd linear sync --prefer-linear
Use when:
First-time import of existing Linear issues:
# Configure credentials
export LINEAR_API_KEY="lin_api_..." # add to ~/.secrets or ~/.zshrc
bd config set linear.team_id "team-uuid"
# Check status
bd linear status
# Import all issues
bd linear sync --pull
# See what was imported
bd stats
bd list --json
Regular synchronization:
# Pull latest from Linear (incremental since last sync)
bd linear sync --pull
# Do local work
bd update bd-123 --claim
# ... work ...
bd close bd-123 --reason "Fixed"
# Push changes to Linear
bd linear sync --push
# Or do full bidirectional sync
bd linear sync
Create issues locally and sync to Linear:
# Create issue locally
bd create "Fix authentication bug" -t bug -p 1
# Push to Linear (creates new Linear issue, updates external_ref)
bd linear sync --push
# Verify
bd show bd-abc # Should have external_ref pointing to Linear
Full migration from Linear to bd:
# Import all issues
bd linear sync --pull --state all
# Preview import
bd stats
# Continue using bd locally, push updates back to Linear
bd linear sync # Regular bidirectional sync
Mirror Linear issues locally without pushing back:
# Only ever pull, never push
bd linear sync --pull
# Set up a cron job or alias
alias bd-mirror="bd linear sync --pull"
bd linear status
Shows:
bd linear status --json
bd linear sync --json
The sync command shows progress:
All configuration keys for Linear integration:
# Required
linear.api_key # Linear API key (or LINEAR_API_KEY env var)
linear.team_id # Linear team UUID
# Automatic (set by bd)
linear.last_sync # ISO8601 timestamp of last sync
# ID generation (optional)
linear.id_mode # hash (default) or db (let bd generate IDs)
linear.hash_length # Hash length 3-8 (default: 6)
# Priority mapping (Linear 0-4 to Beads 0-4)
linear.priority_map.0 # No priority -> ? (default: 4/backlog)
linear.priority_map.1 # Urgent -> ? (default: 0/critical)
linear.priority_map.2 # High -> ? (default: 1/high)
linear.priority_map.3 # Medium -> ? (default: 2/medium)
linear.priority_map.4 # Low -> ? (default: 3/low)
# State mapping (Linear state type/name to Beads status)
linear.state_map.backlog # (default: open)
linear.state_map.unstarted # (default: open)
linear.state_map.started # (default: in_progress)
linear.state_map.completed # (default: closed)
linear.state_map.canceled # (default: closed)
linear.state_map.<custom> # Map custom state names
# Label to issue type mapping
linear.label_type_map.bug # (default: bug)
linear.label_type_map.defect # (default: bug)
linear.label_type_map.feature # (default: feature)
linear.label_type_map.enhancement # (default: feature)
linear.label_type_map.epic # (default: epic)
linear.label_type_map.chore # (default: chore)
linear.label_type_map.maintenance # (default: chore)
linear.label_type_map.task # (default: task)
linear.label_type_map.<custom> # Map custom labels
# Relation mapping (Linear relation type to Beads dependency type)
linear.relation_map.blocks # (default: blocks)
linear.relation_map.blockedBy # (default: blocks)
linear.relation_map.duplicate # (default: duplicates)
linear.relation_map.related # (default: related)
Set the API key:
# Recommended: environment variable
export LINEAR_API_KEY="lin_api_YOUR_KEY" # add to ~/.secrets or ~/.zshrc
Set the team ID:
bd config set linear.team_id "YOUR_TEAM_UUID"
Linear has API rate limits. The client automatically retries with exponential backoff:
bd linear status for configuration issuesFor large projects, initial sync fetches all issues. Subsequent syncs are incremental (only issues changed since linear.last_sync).
bd jira sync)# Initial setup
$ bd init --quiet
$ export LINEAR_API_KEY="lin_api_abc123..." # add to ~/.secrets or ~/.zshrc
$ bd config set linear.team_id "team-uuid-456"
# Check status
$ bd linear status
Linear Sync Status
==================
Team ID: team-uuid-456
API Key: lin_...c123
Last Sync: Never
Total Issues: 0
With Linear: 0
Local Only: 0
# Pull from Linear
$ bd linear sync --pull
→ Pulling issues from Linear...
Full sync (no previous sync timestamp)
✓ Pulled 47 issues (47 created, 0 updated)
✓ Linear sync complete
# Check what we got
$ bd stats
Issues: 47 (42 open, 5 closed)
Types: 23 task, 15 bug, 7 feature, 2 epic
# Create a local issue
$ bd create "New bug from testing" -t bug -p 1
Created: bd-a1b2c3
# Push to Linear
$ bd linear sync --push
→ Pushing issues to Linear...
Created: bd-a1b2c3 -> TEAM-148
✓ Pushed 1 issues (1 created, 0 updated)
✓ Linear sync complete
# Full bidirectional sync
$ bd linear sync
→ Pulling issues from Linear...
Incremental sync since 2025-01-17 10:30:00
✓ Pulled 3 issues (0 created, 3 updated)
→ Pushing issues to Linear...
✓ Pushed 2 issues (0 created, 2 updated)
✓ Linear sync complete