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

# GitLab CI

> Sync code-managed flows from a GitLab repository with a CI job

Keep your [code-managed flows](/code-managed-flows) in a GitLab repository, self-hosted or on gitlab.com. A CI job sends the repository's `.autosana/` folder to Autosana. Autosana never connects to your GitLab server.

* **On the default branch**, the job updates your tests in Autosana, as a push to the default branch does on GitHub.
* **In a merge request**, the job checks the files and reports errors. Nothing is saved.

When a file is invalid, the job fails and prints each error with its file and line.

## Set up

<Steps>
  <Step title="Add your API key to GitLab">
    Create an API key in [Settings → API Keys](https://autosana.ai/settings?tab=api-keys). In your GitLab project, open **Settings → CI/CD → Variables** and add `AUTOSANA_API_KEY` with the key as its value.

    Mark it **Masked**. Leave **Protect variable** unchecked: GitLab withholds protected variables from merge request pipelines on unprotected branches, so those jobs would fail.
  </Step>

  <Step title="Add the job">
    Add this job to `.gitlab-ci.yml`. It needs the `autosana` CLI 0.9.19 or later, which `pip install autosana` installs.

    ```yaml .gitlab-ci.yml theme={null}
    autosana-sync:
      image: python:3.12-slim
      rules:
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      script:
        - pip install autosana
        - autosana flows sync
    ```

    This job runs in merge request pipelines. If your project's other jobs run only in branch pipelines, each push to a branch with an open merge request starts a second pipeline that holds only this job, and a "Pipelines must succeed" check can pass on it alone. To avoid that, set `workflow:rules` so your project uses merge request pipelines, as in GitLab's [switch between branch pipelines and merge request pipelines](https://docs.gitlab.com/ci/yaml/workflow/#switch-between-branch-pipelines-and-merge-request-pipelines) example.
  </Step>

  <Step title="Push to the default branch">
    The first default-branch run connects the project to your Autosana organization. Your tests appear on the **Flows** page, read-only.
  </Step>
</Steps>

<Note>
  Merge requests from forks run in the fork and don't receive your project's variables, so `autosana flows sync` stops with "AUTOSANA\_API\_KEY is not set". Fork changes are checked when they reach your default branch.
</Note>

## Run a merge request's tests

`autosana flows sync` only checks merge request files. To run them, add a job that runs the merge request's `.autosana/` on Autosana's cloud devices:

```yaml .gitlab-ci.yml theme={null}
autosana-mr-tests:
  image: python:3.12-slim
  needs: ["autosana-sync"]
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - pip install autosana
    - autosana flows run --cloud --all --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
```

GitLab checks out a commit rather than a branch, so pass `--branch` or `--app-build-id`. `--branch` picks the newest app build uploaded from that branch, and `--app-build-id` picks a build directly. The app comes from `.autosana/config.yaml` or from flags, as in [Cloud runs](/code-managed-cli#cloud-runs).

The job starts the runs and prints a link to each, then a batch id. It does not wait for results. To wait, poll the [Run Status API](/api-runs#run-status) with that batch id.

## Self-hosted GitLab

Autosana never connects to your GitLab instance. Your runner connects out to Autosana, and the CLI reads only GitLab CI's predefined `CI_*` variables, so the jobs above need GitLab 13.4 or later.

* **Network access.** Runners need outbound HTTPS (port 443) to `backend.autosana.ai`.
* **Proxy.** Set `HTTPS_PROXY`, and `NO_PROXY` if needed, as a CI/CD variable or in the runner's environment. The CLI sends its requests through that proxy.
* **TLS inspection.** If your proxy re-signs HTTPS traffic with an internal certificate authority, set `SSL_CERT_FILE` to a PEM file that holds that CA, or `SSL_CERT_DIR` to a folder of hashed certificates. Either replaces the default trusted CAs, so include any others the job needs.
* **No access to Docker Hub or PyPI.** Pull the Python image from your internal registry and install from your PyPI mirror with `pip install --index-url https://pypi.example.internal/simple autosana`. Or use an image with `autosana` already installed and drop the `pip install` line. The job still needs to reach Autosana.
* **Source links.** **Open in GitLab** links in the dashboard use your project's address from `CI_PROJECT_URL`, such as `https://gitlab.example.internal/group/project`. They open only for people who can reach your GitLab, for example on your network or VPN. `http://` addresses work too.

Everything in [Set up](#set-up) applies as on gitlab.com: a masked, unprotected `AUTOSANA_API_KEY`, merge request pipelines, the `workflow:rules` advice, and merge requests from forks.

## What the job reports

| Result | When | Job |
| - | - | - |
| `Synced N files from commit <sha> to Autosana.` | A default-branch upload was applied. | passes |
| `N files checked with no errors. Merge request uploads are checked, not saved.` | A merge request upload has no errors. | passes |
| `Autosana already synced a newer commit, so nothing changed.` | A later pipeline synced first. This is normal. If this commit's timestamp is wrong and it must win, re-run with `--force`. | passes |
| `Code-managed tests are turned off for this project in Autosana, so nothing changed.` | Code-managed tests are turned off for this project. Contact Autosana support to turn them back on. | passes |
| `Autosana rejected this upload with N errors. Your tests did not change.` | At least one file is invalid. Each error is printed above it as `path:line  error  message`. | fails |

One invalid file blocks the whole upload: nothing changes until every file is valid, as on [GitHub](/code-managed-sync#sync-status-&-validation).

### Flags

* `--force` applies this commit even when Autosana already synced a newer one. Use it only when this commit's timestamp is wrong. It works on the default branch only. In a merge request pipeline the CLI refuses it and sends nothing.
* `--allow-empty` sends a `.autosana/` folder that holds no `*.flow.yaml` files. **On the default branch, this removes every test the project synced.** Use it only to delete every test on purpose. Without the flag, the CLI refuses such a folder. A missing `.autosana/` folder is refused even with the flag.
* `--api-url`, or the `AUTOSANA_API_URL` variable, sets the Autosana server. Leave it unset unless Autosana gave you a different URL.

### Exit codes

| Code | Meaning | What to do |
| - | - | - |
| `0` | Synced, checked, already synced, or turned off. | Nothing. |
| `1` | Autosana rejected a file, refused the upload, or could not be reached. | Read the message, then see [Troubleshooting](#troubleshooting). |
| `2` | The CLI stopped before uploading. Nothing was sent. | Fix the setup the message names. See [The job exits with code 2](#the-job-exits-with-code-2). |

## Troubleshooting

### The job exits with code 2

The CLI stopped before sending anything. The message names the fix:

* `runs only inside a GitLab CI job`: run it as a GitLab CI job. To check files locally, run `autosana flows validate`.
* `GitLab CI did not set …`: run the job in a default-branch pipeline or a merge request pipeline. If the message adds "Tag pipelines cannot sync.", remove the job from tag pipelines.
* `AUTOSANA_API_KEY is not set`: add the variable, masked and not protected.
* `The Autosana API key contains spaces or line breaks`: copy the key into the variable again, without them.
* `No .autosana/ folder`, or `holds no flow files`: commit `.autosana/` at the repository root, and check that the job checks out the repository.
* `holds symlinks`, or `.autosana is a symlink`: replace each symlink with the file or folder it points to.
* `Could not read …`, or `File is not valid UTF-8 text`: the job cannot read a file in `.autosana/`. Fix or remove the file the message names.
* `--force applies only to default-branch pipelines.`: remove `--force` from merge request jobs.
* `--api-url must be an http or https URL`: fix `--api-url` or `AUTOSANA_API_URL`.

### Autosana refused the upload (HTTP …)

The job exits with code 1 and prints Autosana's reason.

| HTTP | Meaning | What to do |
| - | - | - |
| 400 | The pipeline is neither on the default branch nor a merge request, a GitLab variable is malformed, or the commit timestamp is more than 5 minutes in the future. | Read the printed reason. Run the job only in the pipelines above. |
| 401, 403 | The API key is missing or invalid. | Check `AUTOSANA_API_KEY`. |
| 404 | The URL the CLI reached is not an Autosana server with GitLab sync. | Check `--api-url` or `AUTOSANA_API_URL`. Leave both unset to use Autosana's server. |
| 409 | Your plan is inactive, or your organization already connects a GitHub repository at the same path. | Renew your plan, or disconnect that GitHub repository. In a merge request, the path conflict is reported as a file error instead. |
| 413 | `.autosana/` holds more than 1,000 files or more than 4,000,000 bytes. | Remove files from `.autosana/`. |
| 5xx, or any other code | Autosana had a problem, or the URL is not Autosana's. | Re-run the job. If it fails again, check `--api-url` or `AUTOSANA_API_URL`. |

If the job says `Autosana answered with an unknown status`, update the CLI. The job above installs the newest version on each run, so re-run it.

### Autosana may have applied this upload

The connection broke after the upload was sent, so the job cannot tell whether it was applied. Re-run the job. Re-sending the same commit is safe.

The CLI already retries once, 5 seconds later, when Autosana is briefly unavailable or the upload never reached it. If it still fails with `Failed to reach …`, nothing was applied: check your runner's [network access](#self-hosted-gitlab) to Autosana.

## Limits

* `.autosana/` must be at the repository root. **Root directory** for monorepos is GitHub-only.
* GitLab-synced tests are read-only in the dashboard, and their source links open GitLab.
* Branch runs need GitHub. The dashboard hides branch choices for GitLab repositories, and `autosana run` without `--local` or `--cloud` reads from GitHub. For a GitLab repository, use `autosana flows run --cloud`.
* Results appear in the job log, not as merge request comments or commit statuses.
* There is no resync button. Re-run the default-branch pipeline instead.

## Next Steps

* [Learn the file schema →](/code-managed-files)
* [Run your tests from the terminal →](/code-managed-cli)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.