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:
{
  "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." },

  "source": "linear",

  "linear": {
    "apiKey": "lin_api_..."
  }
}
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:
ekso migrate linear list-projects \
    --config migration.config.json \
    --url https://ekso.acme.com
Sample output:
NAME                  KEY            ID                                       ISSUES
Dotcom Rebuild        ENG/dotcom     01a3b9c8-1f44-4eaa-9c6e-7d5b2c1d3f4e     312
Helpdesk              OPS/helpdesk   02b4ca99-2055-5fbb-ad7f-8e6c3d2e4a5f     128
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:
ekso migrate linear collect \
    --config migration.config.json \
    --project ENG/dotcom \
    --url https://ekso.acme.com
Repeat --project for multiple. Sample run:
fetching projects... 1 fetched
fetching dotcom issues... 312/312  ok
fetching comments...      842/842  ok
fetching attachments...    47/47   ok
saved cache: ~/.ekso/migrate/linear-2026-04-29T143205.sqlite
Useful flags:
  • --no-attachments — skip downloads.
  • --no-comments — skip comments.
  • --resume — pick up where a killed collect left off.

Step 3 — dry-run apply

ekso migrate linear apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com \
    --dry-run
Sample output:
DRY-RUN — no writes.
would create:  1 container, 28 users, 843 items, 2204 annotations,
               156 files, 47 links, 6 cycles
field-map check: ok (1 process field would be auto-created — Estimate)

Step 4 — apply for real

ekso migrate linear apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com
If the run is interrupted, re-run with --resume:
ekso migrate linear apply \
    --config migration.config.json \
    --process proc_engineering \
    --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:
linear:
  estimate: { ekso: Estimate, kind: decimal }
Pass it to apply:
ekso migrate linear apply \
    --config migration.config.json \
    --process proc_engineering \
    --field-map migration.fields.yaml
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