> ## Documentation Index
> Fetch the complete documentation index at: https://ekso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Exit codes, common errors per source, and how to recover from a partial migration.

`ekso migrate` follows the same exit-code convention as the rest of the CLI ([Configuration](/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:

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.

```bash theme={null}
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` (Cloud)                | Wrong email or expired API token                                                       | Regenerate the token at id.atlassian.com → Security → API tokens. Check the email matches the token owner.                                                      |
| `exit 3 — Jira credentials invalid` (Data Center / Server) | Wrong, expired, or revoked Personal Access Token (PAT)                                 | Regenerate the PAT in Jira under your profile → Personal Access Tokens. Requires Jira 8.14 or later.                                                            |
| `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.                                                      |
| Some attachments skipped during a Data Center collect      | An SSO-fronted instance blocked the download session, so a file couldn't be fetched    | Expected; issues, comments, and worklogs still migrate. Download the skipped files manually — see the Data Center notes in the [Jira guide](/cli/migrate/jira). |
| `Custom field unknown to field-map`                        | YAML references a `customfield_*` ID that doesn't exist on this Jira instance          | Check the ID via the discovery `curl` command in [Field mapping](/cli/migrate/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](https://github.com/EksoHQ/CLI/issues) with the cache file (sanitised) and the verbose log.

## Where to next

* **[Command reference](/cli/migrate/command-reference)** — every command and flag.
* **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive.
* **[Field mapping](/cli/migrate/field-mapping)** — `migration.fields.yaml` format.
