Skip to main content
ekso migrate linear pulls projects, issues, comments, attachments, cycles, and labels from Linear (GraphQL API) into your Ekso tenant. Two commands — collect and apply. Read Migrate overview and Before you start first.

What gets imported

Linear conceptEkso shape
ProjectDataContainer
Issue (ENG-123)DataItem
CommentDataAnnotation
File attachmentDataFile (multipart upload)
Subtask, Relates, Blocks, DuplicateDataLink
CycleDataCycle
Label (issue-level)Pipe-delimited string in a Label custom field on the DataItem
Cross-team ProjectConfigLabel (prefix project:)
UserDataUser (matched or minted — Linear users always have email)
Estimate (decimal)DataItem.Field[] (via --field-map)
Linear stores descriptions and comments as markdown directly — no HTML rendering step. The migrator preserves markdown bodies through to Ekso.

What does NOT get imported

  • Workflow states. Linear’s per-team workflow is custom; only the current state of each issue is preserved.
  • URL-bookmark “attachments”. Linear lets you attach a URL as if it were a file; these get logged in Meta.linear_attachments[] rather than uploaded.
  • Triage queues. Triage is a Linear-specific workflow concept; the items themselves migrate but the triage state doesn’t.
  • Customer requests. Linear’s customer-feedback feature isn’t modelled in Ekso v1.
  • Roadmap initiatives. Out of scope for v1.

Before you start

Pre-flight checklist on top of the general one:
  • A Linear workspace where you have admin or read-everything access.
  • A Linear personal API key (free tier supports it).
Add a linear block to your migration.config.json:
Or use the env-var override EKSO_MIGRATE_LINEAR_KEY for CI use cases.

Step 1 — list projects

Verify your credentials work and see what Linear shows you:
Sample output:
The KEY column is what you pass to --project on collect.

Step 2 — collect

Pull a single project’s issues, comments, and attachments into a local SQLite cache:
Repeat --project for multiple. Sample run:
Useful flags:
  • --no-attachments — skip downloads.
  • --no-comments — skip comments.
  • --resume — pick up where a killed collect left off.

Step 3 — dry-run apply

Sample output:

Step 4 — apply for real

If the run is interrupted, re-run with --resume:

Identity resolution for Linear

Linear users always have an email — the default --user-strategy match-or-create is reliable here. No special handling needed. Read Identity resolution.

Custom fields for Linear

Linear’s main custom field is the first-class estimate (a decimal). Most teams need only this one mapping:
Pass it to apply:
If your destination process doesn’t yet have an Estimate field, the Apply layer auto-creates it before any item write. See Field mapping. Without --field-map, the estimate falls through to DataItem.Meta.linear_estimate losslessly.

Cycles, Labels, Cross-team Projects

LinearEkso
CycleDataCycle (always — no flag)
Issue-level labelPipe-delimited string on the DataItem’s Label custom field
Cross-team projectConfigLabel (prefix project:)
Linear labels are imported at the issue level only — workspace-wide and team-wide label definitions don’t migrate as standalone records. Each issue’s label set is concatenated into a pipe-delimited string and written to a custom field named Label on the resulting DataItem (e.g. Bug|Customer-impact|Needs-design). The Label field is auto-created on the destination process if missing. Linear “projects” that span multiple teams stay as project:-prefixed ConfigLabels — single-team projects become containers (per the table above), so the cross-team variety is the only one that needs the label fallback. Cycles attach to a board on the Ekso side. Pass --board <BOARD_ID> if the tenant has more than one board.

Markdown bodies

Linear stores issue descriptions and comments as markdown natively. The migrator passes the markdown through unchanged — Ekso renders it the same way Linear did. You shouldn’t see any visual drift on body content.

Iron rule — cursor pagination terminator

Linear’s GraphQL API paginates with cursors. The pagination loop terminates only when the response returns both HasNextPage=false and a non-null EndCursor. An earlier version of the upstream adapter terminated only on HasNextPage, which produced infinite loops on certain edge-case responses (Linear sometimes returns HasNextPage=true with a null EndCursor). The current CLI handles this correctly with a regression test. If you ever see Inconsistent pagination response from Linear, the CLI is detecting the edge case and exiting cleanly rather than looping. Re-run collect to retry.

Troubleshooting Linear-specific issues

SymptomFix
exit 3 — Linear API key invalidMint a new key at linear.app → Settings → API → Personal API keys.
URL-bookmark attachment skippedLogged in Meta.linear_attachments[]. Documented limitation — Linear “attachments” can be URLs rather than files.
Inconsistent pagination response from LinearThe pagination terminator detected an edge-case response. Re-run collect.
exit 7 — Linear rate-limit exceededLinear’s GraphQL complexity budget got exhausted. Wait, then re-run with --resume.
See Troubleshooting for the full per-source error table.

Why migrate to Ekso

Linear is excellent at issue tracking. Ekso adds financial intelligence, multi-tenant SaaS or self-host, and AI-native primitives that Linear doesn’t model. See Ekso vs Linear for the broader comparison.

Where to next