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 concept | Ekso shape |
|---|---|
| Project | DataContainer |
Issue (ENG-123) | DataItem |
| Comment | DataAnnotation |
| File attachment | DataFile (multipart upload) |
| Subtask, Relates, Blocks, Duplicate | DataLink |
| Cycle | DataCycle |
| Label (issue-level) | Pipe-delimited string in a Label custom field on the DataItem |
| Cross-team Project | ConfigLabel (prefix project:) |
| User | DataUser (matched or minted — Linear users always have email) |
| Estimate (decimal) | DataItem.Field[] (via --field-map) |
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).
linear block to your migration.config.json:
EKSO_MIGRATE_LINEAR_KEY for CI use cases.
Step 1 — list projects
Verify your credentials work and see what Linear shows you: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:--project for multiple. Sample run:
--no-attachments— skip downloads.--no-comments— skip comments.--resume— pick up where a killed collect left off.
Step 3 — dry-run apply
Step 4 — apply for real
--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-classestimate (a decimal). Most teams need only this one mapping:
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
| Linear | Ekso |
|---|---|
| Cycle | DataCycle (always — no flag) |
| Issue-level label | Pipe-delimited string on the DataItem’s Label custom field |
| Cross-team project | ConfigLabel (prefix project:) |
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 bothHasNextPage=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
| Symptom | Fix |
|---|---|
exit 3 — Linear API key invalid | Mint a new key at linear.app → Settings → API → Personal API keys. |
URL-bookmark attachment skipped | Logged in Meta.linear_attachments[]. Documented limitation — Linear “attachments” can be URLs rather than files. |
Inconsistent pagination response from Linear | The pagination terminator detected an edge-case response. Re-run collect. |
exit 7 — Linear rate-limit exceeded | Linear’s GraphQL complexity budget got exhausted. Wait, then re-run with --resume. |
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
- Command reference — full flag surface.
- Identity resolution —
--user-strategydeep-dive. - Field mapping — write your
migration.fields.yaml. - Troubleshooting — exit codes and recovery.