Skip to main content

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

CodeMeaning during migrate
0Success. The cache was written (collect) or every cached row was applied (apply).
1Generic error — check the message above.
2Usage error. Bad flags, missing required argument (e.g. apply without --process).
3Auth error. Either Ekso credentials or source-platform credentials rejected.
4Forbidden. The Ekso API key lacks permission to create one of the entity types.
5Not found. Source project/team/org doesn’t exist or the cache file is missing.
6Validation error. Custom-field auto-create failed, or an item POST got a 422 from Ekso.
7Rate limited. Either the source platform or Ekso returned 429 too many times.
8Network error. SQL connection refused, DNS failure, or transient HTTP error past retry.
9Server 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:
  1. Read the error message — it tells you which row failed and why.
  2. Fix the underlying cause (a bad value in the cache, a missing field-map entry, a permissions issue on the destination).
  3. 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

SymptomCauseFix
exit 3 — Jira credentials invalidWrong email or expired API tokenRegenerate the token at id.atlassian.com → Security → API tokens
exit 7 — Jira rate-limit exceededAtlassian returned 429 past the retry budgetWait a few minutes; Atlassian’s rate limits are per-tenant per-IP. Re-run with --resume.
Item created with empty body warningAtlassian returned no rendered HTML for ADF descriptionItem is created; the original ADF JSON is preserved in Meta.jira_description_html. Often harmless.
Date format unparseableAtlassian returned a date with a no-colon offset (+0000) the standard parser rejectsAlready handled by JiraDateTimeNormaliser — if you see this, the cache is from an older CLI; re-collect.
Custom field unknown to field-mapYAML references a customfield_* ID that doesn’t exist on this Jira tenantCheck the ID via the discovery curl command in Field mapping.

Linear

SymptomCauseFix
exit 3 — Linear API key invalidWrong or revoked API keyMint a new one at linear.app → Settings → API → Personal API keys
Inconsistent pagination response from LinearLinear returned HasNextPage=true with a null EndCursorThe CLI’s pagination terminator handles this — it logs a safety break and exits cleanly. Re-run collect to retry.
URL-bookmark attachment skippedLinear “attachments” are sometimes URL bookmarks, not filesLogged in Meta.linear_attachments[]. No file is uploaded. Documented limitation.
Cycle creates without items warningA cycle was empty in Linear at collect timeCycle is created on the Ekso side anyway — populate it later when items move into it.

Azure DevOps

SymptomCauseFix
exit 3 — DevOps PAT invalidPersonal Access Token expired or wrong scopeMint a new PAT with Work Items (Read) + Identity (Read) scopes.
exit 7 — VssServiceResponseException 429DevOps returned 429 past retryWait — 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 userFiltered at collect time. No action needed.
Iteration hierarchy flattenedDevOps iterations like MyProj\Sprint 1\Week 1 flatten to Sprint 1 / Week 1Documented limitation. Hierarchical cycle support is on the v2 roadmap.
Work item type not on destination processSource had a Bug type the destination process doesn’t modelItem is created; type preserved in Meta.devops_workitemtype.

Zendesk

SymptomCauseFix
exit 3 — Zendesk credentials invalidWrong email/token comboTokens use email/token:API_TOKEN basic auth; check both.
Synthetic container createdZendesk instance has no orgsExpected behaviour — every ticket lands in a single zendesk-default container.
Anonymous end-user minted with placeholder emailSource user has no emailTagged migrated-from:zendesk-anon in Ekso. Use --user-strategy migration-bot if you don’t want these.
Attachment over 20MB skippedEkso file-size limit exceededSkipped with warning; file URL preserved in Meta.
Public/private comment flagZendesk has visibility flagsPreserved as annotation Tags=["public"] or ["private"].

Gemini

SymptomCauseFix
SQL connection refusedNetwork can’t reach the Gemini DBSwitch to --connection-mode api, or run the migration from inside the corporate network where SQL is reachable.
SQL credentials invalidDB user doesn’t have read on Gemini tablesGrant db_datareader to the migration user on the Gemini DB.
API 401Gemini API key wrong / Gemini admin disabledGet a fresh API key from the Gemini admin surface.
Schema drift — unknown columnGemini schema differs from the supported versionSurfaced in Meta.gemini_unmapped_columns. The migration completes; data shape just differs slightly.
Mode mismatch — SQL configured but only API accessconnectionMode=sql but the SQL host isn’t reachablePass --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

SituationAction
Cache file was generated by a different CLI versionRe-collect. Schema-version mismatch refuses to apply.
Source platform data has changed since collectRe-collect to refresh. The new collect writes a new timestamped cache; old cache is still around.
Apply was interrupted mid-flightRe-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 applyManually 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