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 follows the same exit-code convention as the rest of the CLI (Configuration) plus a few migration-specific patterns. This page covers what each code means in context, common per-source failures, and how to recover.
Exit codes in context
| Code | Meaning during migrate |
|---|
0 | Success. The cache was written (collect) or every cached row was applied (apply). |
1 | Generic error — check the message above. |
2 | Usage error. Bad flags, missing required argument (e.g. apply without --process). |
3 | Auth error. Either Ekso credentials or source-platform credentials rejected. |
4 | Forbidden. The Ekso API key lacks permission to create one of the entity types. |
5 | Not found. Source project/team/org doesn’t exist or the cache file is missing. |
6 | Validation error. Custom-field auto-create failed, or an item POST got a 422 from Ekso. |
7 | Rate limited. Either the source platform or Ekso returned 429 too many times. |
8 | Network error. SQL connection refused, DNS failure, or transient HTTP error past retry. |
9 | Server error. The source platform or Ekso returned 5xx past retry. |
The CLI prints a structured error block on every non-zero exit identifying which row in the cache (and which field, when relevant) caused the failure.
Recovering from a partial apply
Apply is idempotent and resumable. If something fails halfway:
- Read the error message — it tells you which row failed and why.
- Fix the underlying cause (a bad value in the cache, a missing field-map entry, a permissions issue on the destination).
- Re-run apply with
--resume. Already-applied rows are skipped via the IdMap; the run picks up at the failed row.
ekso migrate jira apply \
--config migration.config.json \
--process proc_engineering \
--resume
If the failure is structural (the cache is wrong shape because you upgraded the CLI between collect and apply), re-run collect to rebuild the cache, then re-run apply.
Common per-source errors
Jira
| Symptom | Cause | Fix |
|---|
exit 3 — Jira credentials invalid | Wrong email or expired API token | Regenerate the token at id.atlassian.com → Security → API tokens |
exit 7 — Jira rate-limit exceeded | Atlassian returned 429 past the retry budget | Wait a few minutes; Atlassian’s rate limits are per-tenant per-IP. Re-run with --resume. |
Item created with empty body warning | Atlassian returned no rendered HTML for ADF description | Item is created; the original ADF JSON is preserved in Meta.jira_description_html. Often harmless. |
Date format unparseable | Atlassian returned a date with a no-colon offset (+0000) the standard parser rejects | Already handled by JiraDateTimeNormaliser — if you see this, the cache is from an older CLI; re-collect. |
Custom field unknown to field-map | YAML references a customfield_* ID that doesn’t exist on this Jira tenant | Check the ID via the discovery curl command in Field mapping. |
Linear
| Symptom | Cause | Fix |
|---|
exit 3 — Linear API key invalid | Wrong or revoked API key | Mint a new one at linear.app → Settings → API → Personal API keys |
Inconsistent pagination response from Linear | Linear returned HasNextPage=true with a null EndCursor | The CLI’s pagination terminator handles this — it logs a safety break and exits cleanly. Re-run collect to retry. |
URL-bookmark attachment skipped | Linear “attachments” are sometimes URL bookmarks, not files | Logged in Meta.linear_attachments[]. No file is uploaded. Documented limitation. |
Cycle creates without items warning | A cycle was empty in Linear at collect time | Cycle is created on the Ekso side anyway — populate it later when items move into it. |
Azure DevOps
| Symptom | Cause | Fix |
|---|
exit 3 — DevOps PAT invalid | Personal Access Token expired or wrong scope | Mint a new PAT with Work Items (Read) + Identity (Read) scopes. |
exit 7 — VssServiceResponseException 429 | DevOps returned 429 past retry | Wait — DevOps rate limits are tied to throughput units. Re-run with --resume. |
Service identity filtered info | ”Project Collection Build Service” etc. is not a real user | Filtered at collect time. No action needed. |
Iteration hierarchy flattened | DevOps iterations like MyProj\Sprint 1\Week 1 flatten to Sprint 1 / Week 1 | Documented limitation. Hierarchical cycle support is on the v2 roadmap. |
Work item type not on destination process | Source had a Bug type the destination process doesn’t model | Item is created; type preserved in Meta.devops_workitemtype. |
Zendesk
| Symptom | Cause | Fix |
|---|
exit 3 — Zendesk credentials invalid | Wrong email/token combo | Tokens use email/token:API_TOKEN basic auth; check both. |
Synthetic container created | Zendesk instance has no orgs | Expected behaviour — every ticket lands in a single zendesk-default container. |
Anonymous end-user minted with placeholder email | Source user has no email | Tagged migrated-from:zendesk-anon in Ekso. Use --user-strategy migration-bot if you don’t want these. |
Attachment over 20MB skipped | Ekso file-size limit exceeded | Skipped with warning; file URL preserved in Meta. |
Public/private comment flag | Zendesk has visibility flags | Preserved as annotation Tags=["public"] or ["private"]. |
Gemini
| Symptom | Cause | Fix |
|---|
SQL connection refused | Network can’t reach the Gemini DB | Switch to --connection-mode api, or run the migration from inside the corporate network where SQL is reachable. |
SQL credentials invalid | DB user doesn’t have read on Gemini tables | Grant db_datareader to the migration user on the Gemini DB. |
API 401 | Gemini API key wrong / Gemini admin disabled | Get a fresh API key from the Gemini admin surface. |
Schema drift — unknown column | Gemini schema differs from the supported version | Surfaced in Meta.gemini_unmapped_columns. The migration completes; data shape just differs slightly. |
Mode mismatch — SQL configured but only API access | connectionMode=sql but the SQL host isn’t reachable | Pass --connection-mode api to override at the command line. |
How to read the apply summary
After apply completes (success or failure), the CLI prints a per-entity summary:
applying container ACME... ok
applying users (47)... ok (45 created, 2 matched)
applying items (1247)... ok (1247 created, 0 skipped)
applying annotations (4083)... ok (4071 created, 12 errors)
applying files (312)... ok (308 created, 4 skipped)
applying links (89)... ok
applying cycles (8)... ok
done in 7m43s — 12 non-fatal errors, exit 0
The columns you care about:
- created — new rows written to Ekso.
- matched — existing Ekso rows reused (users, mostly).
- skipped — rows the IdMap said were already applied (typical on
--resume).
- errors — non-fatal failures (e.g. attachment too big, comment with malformed HTML). Apply continues; exit code reflects whether any errors occurred.
If the summary shows non-zero errors, scroll up — every error has a structured log entry above the summary identifying the source-id that failed and the specific Ekso response.
When to re-collect vs re-apply
| Situation | Action |
|---|
| Cache file was generated by a different CLI version | Re-collect. Schema-version mismatch refuses to apply. |
| Source platform data has changed since collect | Re-collect to refresh. The new collect writes a new timestamped cache; old cache is still around. |
| Apply was interrupted mid-flight | Re-apply with --resume. Don’t re-collect. |
| Apply found a bad value in the cache (e.g. malformed date) | Re-collect — fixing the cache by hand is fragile. The new CLI usually has fixes. |
| You want a clean tenant after a failed apply | Manually delete the partially-applied entities on the Ekso side, then re-apply. The IdMap keeps the migration idempotent. |
Getting help
- The CLI’s
--help reflects the binary you have installed — treat it as the source of truth if these docs and your CLI ever drift.
- Run with
--verbose to see request/response details.
- Open an issue at github.com/EksoHQ/CLI/issues with the cache file (sanitised) and the verbose log.
Where to next