Documentation Index
Fetch the complete documentation index at: https://ekso.dev/llms.txt
Use this file to discover all available pages before exploring further.
ekso migrate linear pulls teams, 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 |
|---|
Team (ENG, OPS) | DataContainer |
Issue (ENG-123) | DataItem |
| Comment | DataAnnotation |
| File attachment | DataFile (multipart upload) |
| Subtask, Relates, Blocks, Duplicate | DataLink |
| Cycle | DataCycle |
| Label | ConfigLabel |
| Cross-team Project | ConfigLabel (prefix project:) |
| User | DataUser (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": { "tenant": "acme", "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 teams
Verify your credentials work and see what Linear shows you:
ekso migrate linear list-teams \
--config migration.config.json \
--tenant acme
Sample output:
NAME KEY ID MEMBERS
Engineering ENG 01a3b9c8-1f44-4eaa-9c6e-7d5b2c1d3f4e 12
Operations OPS 02b4ca99-2055-5fbb-ad7f-8e6c3d2e4a5f 6
Linear’s list-teams is the equivalent of the other adapters’ list-projects — Linear’s “team” is the security/billing boundary that the migrator treats as a container.
Step 2 — collect
Pull a single team’s issues, comments, and attachments into a local SQLite cache:
ekso migrate linear collect \
--config migration.config.json \
--team ENG \
--tenant acme
Repeat --team for multiple. Sample run:
fetching teams... 1 fetched
fetching ENG issues... 843/843 ok
fetching comments... 2204/2204 ok
fetching attachments... 156/156 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 \
--tenant acme \
--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 \
--tenant acme
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
| Linear | Ekso |
|---|
| Cycle | DataCycle (always — no flag) |
| Label | ConfigLabel (tenant-scoped) |
| Cross-team project | ConfigLabel (prefix project:) |
Linear labels are team-scoped; Ekso ConfigLabels are tenant-scoped. To avoid name collisions across teams, the migrator prefixes labels with the team key when migrating multiple teams: ENG/Bug rather than Bug.
Linear “projects” can span multiple teams — these become project:-prefixed labels rather than containers, so the security/billing moat that team boundaries represent stays intact on the Ekso side.
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.
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
| 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. |
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