# Troubleshoot qf and CI uploads

Source: https://docs.qualflare.com/docs/cli/troubleshooting · Last updated: 2026-10-27

Fix Qualflare CLI and CI upload errors: what each qf exit code means, the exact messages for tokens, environments, formats and timeouts, and how to fix them.

When the Qualflare CLI (`qf`) fails, it prints one `Error:` line and exits with code 1, 3, 4, 5 or 7; code 0 means the upload succeeded, even when tests in the report failed. Find the exact message below for its cause and fix.

## Read an error message

An error from the API names the operation, the server's message, the HTTP status, a hint for some statuses and a request ID:

```text
Error: failed to process test results: send: environment "dev" does not exist in this project (status: 400) [request_id: …]
```

* `failed to process test results:` means `collect` failed while it parsed or uploaded the files.
* `send:` is the upload request, and the text after it is the server's message.
* `(status: 400)` is the HTTP status, which decides the exit code.
* After a `401`, `402` or `403`, a hint follows, such as `— the token lacks access to this project`.
* `[request_id: …]` identifies the request. Include it when you contact support ([Getting Help](https://docs.qualflare.com/docs/faq/getting-help)).

## Exit codes

| Code | Meaning                                                                                                                                                                                           | Retry?                      | Typical message                                            | What to do                                                  |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------- |
| `0`  | Success: the results were uploaded, or validated                                                                                                                                                  | No need                     | `OK Test results collected successfully`                   | Nothing                                                     |
| `1`  | A local or request error: a missing file, a parse error, a bad flag, an unknown environment, milestone or language (`400`), or a request that got no answer, such as a network error or a timeout | Not until you fix the cause | `file does not exist: results.xml`                         | Find the message below                                      |
| `3`  | `401`: the token is invalid or expired                                                                                                                                                            | No                          | `invalid or expired access token (status: 401)`            | Create a new token and run `qf login <identifier> --force`  |
| `4`  | `403` or `402`: access denied, or a plan limit was reached                                                                                                                                        | No                          | `… (status: 403) — the token lacks access to this project` | Restore the token creator's role, or check the plan's usage |
| `5`  | `404`: something the command named wasn't found                                                                                                                                                   | No                          | `… (status: 404)`                                          | Check the sequence number or ID you passed                  |
| `7`  | `429` or a `5xx` status after the retries                                                                                                                                                         | Yes                         | `… (status: 503)`                                          | Temporary: `qf` already retried. Re-run the job             |

## Install and log in

These errors come from installing `qf` and from the token it uploads with.

*Figure: An expired token is marked Expired on the Access Tokens page: create a new token and run qf login again.*

### `command not found: qf` after you install it

**Symptoms:**

* "command not found: qf"
* "permission denied" when you move the binary into place
* The install command fails or hangs

**Possible causes:**

* The directory that holds `qf` is not on your `PATH`
* Your shell has not picked up a `PATH` change yet
* No write access to the target directory, such as `/usr/local/bin`
* Your OS or CPU has no release build. Builds exist for macOS, Linux and Windows on amd64 and arm64.
* A network or proxy problem during the download

**Resolution steps:**

1. Install with a supported method. There is no install script. See [Installation](https://docs.qualflare.com/docs/cli/installation) for Docker and release binaries.
   ```bash
   # macOS or Linux (Homebrew)
   brew install qualflare/tap/qf

   # macOS, Linux or Windows (npm)
   npm install -g @qualflare/cli
   ```

2. If you downloaded a release binary, move it into a directory on your `PATH`:
   ```bash
   sudo mv qf /usr/local/bin/
   ```
   On Windows, move `qf.exe` into a folder on your `PATH`.

3. Open a new terminal, then check that your shell finds `qf`:
   ```bash
   # macOS and Linux
   which qf

   # Windows
   where.exe qf
   ```
   If it is still not found, add the directory that holds `qf` to your `PATH`: in your shell profile on macOS and Linux, or in Environment Variables on Windows.

4. Verify the installation:
   ```bash
   qf version
   ```

### `invalid or expired access token` (exit code 3)

Qualflare calls API keys access tokens.

**Symptoms:**

* `collect` or a read command fails with `invalid or expired access token (status: 401)` and exits with code `3`:
  ```text
  Error: failed to process test results: send: invalid or expired access token (status: 401) — run `qf login <identifier> <token>` to re-authenticate [request_id: …]
  ```

**Possible causes:**

* The token expired. A token stops working after the expiration you chose when you created it.
* Someone revoked the token.
* The token's creator signed out everywhere, reset their password or locked their account, or the workspace turned on a 2FA or SSO requirement.
* The account of the person who created the token was deactivated or deleted.
* The token was copied incompletely. New tokens start with `qfp_`.
* The job still sets `QF_API_KEY`, which `qf` no longer reads. {/* allow-claim: qf-api-key — names the removed variable so old pipelines can be fixed */}

**Resolution steps:**

1. Create a new token:
   * In your project, open **Settings** → **Access Tokens**
   * Click **Create Access Token**
   * Copy the token. It is shown only once.

2. Save the new token under your identifier. `--force` replaces the old one:
   ```bash
   qf login myapp --force
   ```
   In CI, update the `QF_TOKEN` secret instead. `qf login ci --force` reads it.

3. Check the token with a real request. `--dry-run` never contacts Qualflare, so it cannot test a token:
   ```bash
   qf myapp launches list
   ```

### `the token lacks access to this project` (exit code 4)

A `403` means the token is valid, but the person who created it no longer has the role the command needs on the project: Editor or higher for `collect`, Viewer or higher for the read commands. The token's own access level is not the cause: **Read & upload** is enough for `collect`.

```text
Error: failed to process test results: send: no project access (status: 403) — the token lacks access to this project [request_id: …]
```

Ask a project Maintainer or Owner to restore the role, or create a new token as someone who has it. [Roles and permissions](https://docs.qualflare.com/docs/administration/roles-permissions) lists what each role can do.

### `identifier "ci" already exists` when you log in

`qf login` asks before it replaces a saved identifier, and with no terminal it can't ask, so it stops:

```text
Error: identifier "ci" already exists; cannot prompt because stdin is not a terminal — use --force to overwrite
```

Add `--force`: `qf login ci --force`. It happens on a CI runner that keeps its home directory between jobs.

## Upload results

### `file does not exist`, `no files match pattern` or `failed to parse file`

**Symptoms:**

* "file does not exist" error
* "no files match pattern" error
* "failed to parse file" error
* Upload hangs or times out

**Possible causes:**

* Incorrect file path
* A glob or directory that matches no report
* Invalid file format
* Network connectivity issues
* API endpoint unreachable

**Resolution steps:**

1. Verify file exists:
   ```bash
   ls -la test-results.xml
   pwd  # Check current directory
   ```

2. Use absolute path:
   ```bash
   qf myapp collect /full/path/to/test-results.xml
   ```
   A glob that matches nothing fails with `no files match pattern`, and a directory with no `.json` report directly inside it fails with `contains no .json report files`: see [Collect a directory](https://docs.qualflare.com/docs/cli/sharding#collect-a-directory).

3. Validate file format:
   ```bash
   # Dry run to check parsing
   qf myapp collect test-results.xml --dry-run --verbose

   # Check file format
   qf validate test-results.xml
   ```

4. Raise the overall deadline for large files (default `30s`):
   ```bash
   qf myapp collect test-results.xml --timeout 2m
   ```

5. Check network connectivity. The API answers `{"status":"ok"}`:
   ```bash
   # Test API endpoint
   curl -fsS https://api.qualflare.com/health
   ```

6. If `collect` fails with `the results are …, over /collect's 10 MB limit even without attachments; split the upload, e.g. collect fewer files per run`, upload fewer files per `collect`: see [Size limits](https://docs.qualflare.com/docs/cli/artifacts#upload-limits).

### `unsupported format`, or qf reads the wrong format

**Symptoms:**

* Error: "unsupported format"
* CLI cannot identify test framework
* Auto-detection picks wrong format

**Possible causes:**

* Unsupported test framework
* Non-standard file format
* Missing format specification
* Ambiguous file content

**Resolution steps:**

1. List supported formats:
   ```bash
   qf list-formats
   ```

2. Specify format explicitly:
   ```bash
   qf myapp collect results.xml --format junit
   qf myapp collect results.json --format playwright
   ```
   JUnit-style XML from TestNG, Maestro, XCTest, Espresso or PHPUnit always needs its `--format` value: see [How qf picks a format](https://docs.qualflare.com/docs/cli/formats#auto-detection).

3. Validate file before uploading:
   ```bash
   qf validate results.json --format your-framework --verbose
   ```

4. Check file content:
   ```bash
   # View first few lines
   head -20 results.xml

   # Verify format structure
   cat results.xml | grep -E '<testsuite|<testcase'
   ```

5. Convert to supported format:
   * If using unsupported framework, convert to JUnit XML
   * Many test frameworks have JUnit XML reporters
   * If your framework has an Allure adapter, upload its results directory instead (see [Allure results](https://docs.qualflare.com/docs/cli/formats#allure))

### `environment "staging" does not exist in this project`

**Symptoms:**

* Results show under the wrong environment
* The upload fails with `environment "staging" does not exist in this project (status: 400)`

**Possible causes:**

* No environment was passed, so `qf` used `development`
* The value doesn't match an environment **Identifier** exactly
* The environment doesn't exist in this project yet

**Resolution steps:**

1. Find the Identifier:
   * In the project, open **Settings** → **Environments**
   * The Identifier is the smaller text under each environment's name
   * Every new project starts with `development`, `staging`, `production` and `qa`

2. Create a missing environment:
   * Click **Create Environment**
   * Fill in **Identifier**, **Name** and **Description** (at least 3 characters each), then click **Create**
   * You need the Editor role or higher on the project

3. Pass the Identifier when you upload:
   ```bash
   qf myapp collect results.xml --environment staging
   ```
   You can set `QF_ENVIRONMENT` instead. `--environment` wins when both are set. If you set neither, `qf` uses the environment recorded by a native reporter, or `development`.

4. Match the case exactly:
   * Identifiers are case-sensitive: `Production` and `production` are different
   * An upload with an unknown environment fails with HTTP 400 and exits with code `1`

A milestone or language the project doesn't have fails the same way, with `400` and exit code `1`: `milestone #12 does not exist in this project` or `language "xx-XX" is not supported`. See [Launch metadata](https://docs.qualflare.com/docs/cli/collect#launch-metadata) for each field's flag, variable and default.

### `a plan limit was reached` (exit code 4)

A `402` means the workspace reached a limit of its plan:

```text
Error: failed to process test results: send: … (status: 402) — a plan limit was reached; check your Qualflare subscription [request_id: …]
```

The workspace Owner can check usage under **Plan & Billing**. A retried upload of a launch that was already created is not refused. See [Plans and limits](https://docs.qualflare.com/docs/administration/plans#limits).

### `context deadline exceeded` during an upload

**Symptoms:**

* Upload takes too long
* "timeout" error after 30 seconds
* Large files fail consistently

**Possible causes:**

* File too large
* Slow network connection
* Server overloaded
* Firewall blocking upload

**Resolution steps:**

1. Raise the overall deadline. `--timeout` covers parsing and uploading, retries included, and defaults to `30s`:
   ```bash
   qf myapp collect results.xml --timeout 2m
   ```
   `QF_TIMEOUT` (default `120s`) only caps each single HTTP request. Raise it as well only if one request takes longer than two minutes.

2. Configure retry settings. Retries run inside the `--timeout` deadline, so raise that too:
   ```bash
   export QF_RETRY_MAX=5
   export QF_RETRY_DELAY=2s
   export QF_RETRY_MAX_DELAY=60s
   qf myapp collect results.xml --timeout 5m
   ```

3. Test network speed:
   ```bash
   # Check connection to API
   ping api.qualflare.com

   # Test upload speed
   curl -o /dev/null -s -w "%{time_total}\n" https://api.qualflare.com
   ```

### The upload still fails after retries (exit code 7)

**Symptoms:**

* Upload fails after multiple retries and shows the last attempt's error. The exit code is `7` when the server answered `429` or a `5xx` status, and `1` when the request got no answer, for example after a network error or a timeout
* Intermittent upload failures

**Possible causes:**

* Unstable network connection
* Server temporarily unavailable
* Rate limiting
* Corrupted test result file

`qf` retries a `429`, `500`, `502`, `503` or `504` answer and a network error, 3 times by default and at most 10. Every attempt sends the same `Idempotency-Key`, so a retry never creates a second launch.

**Resolution steps:**

1. Check the exit code. `7` means the server answered `429` or a `5xx` status on every attempt:
   ```bash
   qf myapp collect results.xml; echo "exit code: $?"
   ```

2. Adjust retry configuration:
   ```bash
   # Increase max retries
   export QF_RETRY_MAX=10

   # Increase delay between retries
   export QF_RETRY_DELAY=5s
   export QF_RETRY_MAX_DELAY=120s

   # Retries run inside --timeout, so raise it too
   qf myapp collect results.xml --timeout 10m
   ```

3. Test file integrity:
   ```bash
   # Validate file format
   qf validate results.xml

   # Check file size
   ls -lh results.xml
   ```

4. Exit code `7` marks a temporary failure. Re-run the job.

## CI pipelines

### `token cannot be empty` or `no identifier "ci" configured` in CI

**Symptoms:**

* `qf login` fails with `token cannot be empty`
* `collect` fails with `no identifier "ci" configured`

**Possible causes:**

* The `QF_TOKEN` secret is not set, or the job cannot see it
* The secret has a different name. `qf login` reads only `QF_TOKEN`.
* `qf login` did not run in this job. Each CI job starts with no saved login.

**Resolution steps:**

1. Verify the secret is set:
   * **GitHub Actions**: Settings > Secrets and variables > Actions
   * **GitLab CI**: Settings > CI/CD > Variables
   * **Jenkins**: Manage Credentials > Global credentials

2. Expose it to the job as `QF_TOKEN`, then log in and upload in the same job:
   ```yaml
   - name: Upload to Qualflare
     env:
       QF_TOKEN: ${{ secrets.QF_TOKEN }}
     run: |
       qf login ci --force
       qf ci collect results.xml
   ```

3. Check that the secret reached the job, without printing it:
   ```bash
   [ -n "$QF_TOKEN" ] || { echo "QF_TOKEN is not set"; exit 1; }
   ```

4. Check where the secret is available. Some CI platforms limit secrets to protected branches or environments, so other jobs do not get them.

More on CI logins: [Log in in CI](https://docs.qualflare.com/docs/cli/authentication#log-in-in-ci).

### The upload times out in CI

**Symptoms:**

* CI/CD pipeline times out during upload
* "connection timeout" or "request timeout" error

**Possible causes:**

* CI environment has strict timeout
* Network restrictions in CI environment
* Large test result files
* API endpoint not reachable from CI

**Resolution steps:**

1. Raise the overall upload deadline with `--timeout` (default `30s`):
   ```yaml
   - qf ci collect results.xml --timeout 2m
   ```
   `QF_TIMEOUT` (default `120s`) only caps each single HTTP request. Set it as well only if one request takes longer than two minutes.

2. Configure retry settings:
   ```yaml
   env:
     QF_RETRY_MAX: 5
     QF_RETRY_DELAY: 2s
     QF_RETRY_MAX_DELAY: 60s
   ```

3. Test API connectivity from CI. The API answers `{"status":"ok"}`:
   ```yaml
   - name: Test API connectivity
     run: curl -fsS https://api.qualflare.com/health
   ```

4. Check CI firewall rules:
   * Some CI environments block outbound HTTPS
   * Contact CI platform support for firewall issues

### The launch has no branch or commit

**Symptoms:**

* Git branch and commit show as blank
* Launch metadata missing git information

**Possible causes:**

* Auto-detection not supported by CI platform
* A detached HEAD checkout, which has no branch name
* Environment variable names different

**Resolution steps:**

1. Check current environment variables:
   ```yaml
   - name: Debug CI variables
     run: |
       echo "GIT_BRANCH=$GIT_BRANCH"
       echo "GIT_COMMIT=$GIT_COMMIT"
       echo "GITHUB_REF_NAME=$GITHUB_REF_NAME"
       echo "GITHUB_SHA=$GITHUB_SHA"
   ```

2. Pass the branch and commit on the command line:
   ```bash
   qf ci collect results.xml --branch "$BRANCH_NAME" --commit "$COMMIT_SHA"
   ```

3. Know what `qf` reads on its own. For the branch it uses the first of `QF_BRANCH`, `GIT_BRANCH`, `GITHUB_REF_NAME`, `CI_COMMIT_REF_NAME` and `BITBUCKET_BRANCH` that is set. For the commit it uses `QF_COMMIT`, `GIT_COMMIT`, `GITHUB_SHA`, `CI_COMMIT_SHA` and `BITBUCKET_COMMIT`. If none is set, it asks the local git checkout. A detached HEAD gives no branch, so set `QF_BRANCH` or `--branch` there.

### The results file isn't found in CI

**Symptoms:**

* "file does not exist" error in CI
* Upload command cannot find test results

**Possible causes:**

* Tests not generating output file
* File path incorrect
* Working directory different
* Artifact not created

**Resolution steps:**

1. Verify tests generate output:
   ```yaml
   - name: Run tests
     run: |
       pytest --junitxml=results.xml
       ls -la results.xml  # Verify file exists
   ```

2. Check working directory:
   ```yaml
   - name: Upload results
     run: |
       pwd  # Check current directory
       ls -la  # List files
       qf ci collect test-results/results.xml  # Use correct path
   ```

3. Use absolute path:
   ```yaml
   - run: qf ci collect "$GITHUB_WORKSPACE/test-results/results.xml"
   ```

4. Persist the results file when the upload runs in a later job:
   ```yaml
   # In the test job
   - name: Run tests
     run: pytest --junitxml=results.xml
   - uses: actions/upload-artifact@v4
     with:
       name: test-results
       path: results.xml

   # In the upload job
   - uses: actions/download-artifact@v4
     with:
       name: test-results
   - name: Upload to Qualflare
     run: qf ci collect results.xml
   ```

## Debug an upload

**Enable verbose logging:**

```bash
qf myapp collect results.xml --verbose
```

**Log the full HTTP request and response (the token is redacted):**

```bash
qf myapp collect results.xml --debug
```

**Use dry run to test:**

```bash
qf myapp collect results.xml --dry-run --output json
```

**Check a file without a login:** `qf validate results.xml` parses it without uploading and needs no saved identifier ([`qf validate`](https://docs.qualflare.com/docs/cli/commands#validate-command)).

## Related

* **Guides:** [Authenticate with qf login](https://docs.qualflare.com/docs/cli/authentication) · [CI/CD Integration](https://docs.qualflare.com/docs/cli/ci-cd)
* **Reference:** [Upload results with qf collect](https://docs.qualflare.com/docs/cli/collect) · [Supported formats](https://docs.qualflare.com/docs/cli/formats) · [Configuration and environment variables](https://docs.qualflare.com/docs/cli/configuration)
* **Help:** [Troubleshooting Guide](https://docs.qualflare.com/docs/faq/troubleshooting) (app issues) · [Getting Help](https://docs.qualflare.com/docs/faq/getting-help)
