Files
Eddie the Engineer ebc1800058 fixup(review): close smoke-lane path-filter gap and last_sync doc drift
Maintainer review fixes for PR #6447. No blocker or major findings; this
addresses the synthesis pre-merge checklist plus nearby minor/nit cleanup.

Pre-merge checklist item 1 — the smoke lane's paths: filters gained
cmd/bd/linear_proxy_* but not cmd/bd/linear_mock_fixture_test.go, the fixture
this PR extracted and which both new lane tests require to compile, so a
fixture-only edit skipped the lane until merge_group. Re-derived GitHub's
glob semantics (* does not cross /) over every tracked file: the fix moves
exactly three files DEAF -> MATCH, regresses none, and matches nothing else.

Completeness sweep beyond the reported finding (maintainer scope): the same
grep for the lane's own selector surfaced two pre-existing deaf siblings that
this lane actually executes under -test.run '^TestManagedLocalProxied' --
proxied_outage_reconnect_integration_test.go and
tracker_proxy_uow_conformance_integration_test.go. Both are present at base
and outside this PR's diff; adding a paths: entry can only make the lane fire
more, never less. Verified by running the lane's own -test.list gate locally,
which lists both alongside the nine asserted names.

Pre-merge checklist item 2 — cmd/bd/linear.go is deliberately NOT added to the
filters, and the workflow now says so in a header comment rather than leaving
the lane's production dependency undeclared. The status parity test pins some
of its behavior, but it is a 1.5k-line tracker command whose OAuth, id-mode,
and help-text edits are almost never proxy-relevant; firing a 25-minute netns
lane on all of them costs more than it catches, and merge_group still gates it.

Non-gating cleanup:
- Docs in three sites still presented <tracker>.last_sync as a user-settable
  config key after this PR removed the last config-seam reader -- the exact
  misconception that produced the bug being fixed. Removed it from the
  bd-config-set tables and described it as per-clone local metadata, with the
  information rehomed rather than merely deleted. The generic wording stops
  short of "nothing reads it" because bd jira status still reads its cursor
  from config (see below).
- linearStatusFixture unmarshalled raw CombinedOutput; both status decode
  sites now share parseLinearStatus, matching the skip-to-first-brace idiom
  the sibling roundtrip helpers already use.
- Deduplicated the team-UUID literal onto linearProxyTestTeamID.
- Documented the shared fixture's deliberately-loose cgo build tag.

Validation: actionlint + YAML parse on the workflow; gofmt clean; go vet under
both consumer tag sets (cgo && unix, cgo && integration); the lane's compile
and -test.list presence gate; TestManagedLocalProxiedLinearStatusParity PASS
(32.2s) and TestLinearStatusSQLServerFrontdoor PASS (19.2s); check-doc-flags.sh
PASS and go test ./test/docsync PASS.

Recorded, not fixed here: (1) bd jira status (cmd/bd/jira.go:246) has the
identical config-plane bug this PR fixes for Linear -- out of the PR's declared
Linear scope, warrants a follow-up bead. (2) check-doc-freshness.sh fails on
docs/recovery/init-safety.md (Last reviewed 2026-06-09, 92 days). Verified
pre-existing: the base tree 35fb9c041 fails identically and the file is
untouched by this PR; its marker simply crossed the 90-day limit on 2026-09-07.
Not patched, since bumping the marker would assert a re-review that did not
happen.
2026-09-09 23:12:59 +00:00
..

Linear Integration for bd

Bidirectional synchronization between Linear and bd (beads) using the built-in bd linear commands.

Overview

The Linear integration provides:

  • Pull: Import issues from Linear into bd
  • Push: Export bd issues to Linear
  • Bidirectional Sync: Two-way sync with conflict resolution
  • Incremental Sync: Only sync issues changed since last sync
  • Configurable Mappings: Customize priority, state, label, and relation mappings

Quick Start

1. Get Linear Credentials

  1. API Key: Go to Linear → Settings → API → Personal API keys → Create key
  2. Team ID: Go to Linear → Settings → General → find the Team ID (UUID format)

2. Configure bd

# 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"

3. Sync with Linear

# 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

Authentication

API Key

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_..."

Team ID

Find your Team ID in Linear:

  • Settings → General → Look for Team ID
  • Or extract from URLs: https://linear.app/YOUR_TEAM/... → Go to team settings

Sync Modes

Pull Only (Linear → bd)

Import 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.

Push Only (bd → Linear)

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

Bidirectional Sync

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

Dry Run

Preview what would happen without making changes:

bd linear sync --dry-run

Data Mapping

Priority Mapping

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):

  • 0 (no priority) → 4 (backlog)
  • 1 (urgent) → 0 (critical)
  • 2 (high) → 1 (high)
  • 3 (medium) → 2 (medium)
  • 4 (low) → 3 (low)

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)

State Mapping

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

Label to Issue Type

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

Relation Mapping

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

Conflict Resolution

Conflicts occur when both local and Linear versions are modified since the last sync.

Timestamp-based (Default)

The newer version wins:

bd linear sync  # Newer timestamp wins

Prefer Local

Local bd version always wins:

bd linear sync --prefer-local

Use when:

  • Local is your source of truth
  • You've made deliberate changes locally

Prefer Linear

Linear version always wins:

bd linear sync --prefer-linear

Use when:

  • Linear is your source of truth
  • You want to accept team changes

Workflows

Workflow 1: Initial Import from Linear

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

Workflow 2: Daily Sync

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

Workflow 3: Create Local Issues, Push to Linear

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

Workflow 4: Migrate to bd

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

Workflow 5: Read-Only Linear Mirror

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"

Status & Debugging

Check Sync Status

bd linear status

Shows:

  • Configuration status (API key, team ID)
  • Last sync timestamp
  • Issues with Linear links
  • Issues pending push (local only)

JSON Output

bd linear status --json
bd linear sync --json

Verbose Output

The sync command shows progress:

  • Number of issues pulled/pushed
  • Conflicts detected and resolved
  • Errors and warnings

Configuration Reference

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

# 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)

The incremental-sync cursor is not in this list: bd records it as internal per-clone local metadata, not as project config, so it is never replicated to other clones and bd config set linear.last_sync writes a key nothing reads. Each sync advances it automatically. Read it with bd linear status (the Last Sync line, or last_sync under --json).

Troubleshooting

"Linear API key not configured"

Set the API key:

# Recommended: environment variable
export LINEAR_API_KEY="lin_api_YOUR_KEY"  # add to ~/.secrets or ~/.zshrc

"Linear team ID not configured"

Set the team ID:

bd config set linear.team_id "YOUR_TEAM_UUID"

"GraphQL errors: Not authorized"

  • Verify your API key is correct
  • Check that the API key has access to the team
  • Ensure the key hasn't been revoked

"Rate limited"

Linear has API rate limits. The client automatically retries with exponential backoff:

  • 3 retries with increasing delays
  • If still failing, wait and retry later

"Conflict detection failed"

  • Check network connectivity
  • Verify API key permissions
  • Check bd linear status for configuration issues

Sync seems slow

For large projects, initial sync fetches all issues. Subsequent syncs are incremental (only issues changed since linear.last_sync).

Limitations

  • Single team: Sync is configured per-team (one team_id per bd project)
  • No attachments: Attachments are not synced
  • No comments: Comments are not synced (only description)
  • Custom fields: Linear custom fields are not mapped
  • Projects: Linear projects are not mapped (use labels for categorization)
  • Cycles: Linear cycles/sprints are not mapped

See Also


Example Session

# 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