# Troubleshooting

Start with two commands. They answer most questions:

```sh
qualor validate                        # the resolved configuration, secrets redacted
QUALOR_LOG_LEVEL=debug qualor scan     # every analyzer's command line, output and skip reason
```

## The scan

| Message or symptom | Cause | Fix |
|---|---|---|
| exit 2, `no project key` | outside GitLab/GitHub no CI project path is detected | set `QUALOR_PROJECT_KEY`, `--project-key` or `project.key` |
| exit 2, the URL comes only from `qualor.yml` | the token is sent only to a URL from `QUALOR_URL` or `--server-url` | set `QUALOR_URL` in the CI |
| exit 2, `unknown key analyzers.eslnt` | a typo in `qualor.yml`, because unknown keys are errors | fix the key; the JSON Schema helps in the editor |
| exit 2, `token` key in `qualor.yml` | secrets may not be in the config | remove it and use `QUALOR_TOKEN` |
| exit 2, `p/…` registry id in `semgrep.configs` | registry rules would be fetched over the network | commit the rules and point to them |
| exit 3 | an analyzer with `enabled: true` is missing or crashed. Gitleaks is `true` by default | use the scanner image, or set `enabled: auto` |
| exit 4, connection refused / timeout | the server cannot be reached from the runner | check `QUALOR_URL`, the firewall and `HTTPS_PROXY`/`NO_PROXY` |
| exit 4, certificate error | the server's certificate comes from a private CA | set `QUALOR_CA_FILE` (outside the checkout) or `NODE_EXTRA_CA_CERTS` |
| exit 4, `413` / upload rejected | the report is larger than the server or proxy allows | raise `client_max_body_size` in the proxy, or `QUALOR_UPLOAD_MAX_COMPRESSED_BYTES` |
| exit 4, gate wait timed out | the server is busy, or its worker is stuck | check the server logs. Raise `gate.timeoutSeconds` if analyses are just slow |
| exit 4, `PROJECT_NOT_FOUND` | no project with that key, or the token belongs to another project | create the project, or fix the key |
| exit 5 | the token is invalid, revoked, lacks **Upload analyses**, or is another project's | create a new token |
| `ESLint skipped: no ESLint configuration` | no config at the repository root | add `eslint.config.js`, or set `analyzers.eslint.configFile` |
| ESLint fails, `Cannot find package …` | dependencies are not installed | run `npm ci` (or equivalent) before the scan |
| `… is a legacy eslintrc configuration, which ESLint 9 does not read` | ESLint 9+ reads only flat configs | migrate to `eslint.config.js` |
| `SpotBugs skipped: no compiled classes` | the project was not built | build before the scan, or set `classDirs` |
| `the qualor/scanner image ships no Semgrep rules yet` | no rules are bundled | name your rule files in `analyzers.semgrep.configs`, or ignore the message |
| `VULNERABILITY_DB_STALE` | Trivy's database is more than 14 days old | move to a newer scanner release, or fetch a database in the job with `QUALOR_TRIVY_CACHE_DIR` |
| `ROSLYN_PROJECT_NOT_ANALYZED` | a C# project was not recompiled | build with `--no-incremental` |
| `no C# project was built between qualor dotnet begin and end` | the build ran elsewhere or not at all | run the build in the same job, between the two commands |
| `NESTED_REPOSITORY_SKIPPED` | a submodule or nested clone | expected. Scan that repository on its own |
| Many issues in generated code | generated files are scanned | add them to `sources.exclude` |

## The gate

| Symptom | Cause | Fix |
|---|---|---|
| gate `error`, "new code unavailable" | shallow clone, and the baseline could not be fetched | `GIT_DEPTH: 0` (GitLab), `fetch-depth: 0` (GitHub), `git fetch --unshallow` elsewhere |
| every MR analysis shows up as a branch | the job ran in a branch pipeline | GitLab: use `merge_request_event` rules. Other CI: pass `--mr` and `--mr-target` |
| coverage condition shows "no value" | no coverage report was imported | run the tests with coverage before the scan and list the report in `coverage.reports` |
| coverage is 0 % for files that are tested | the report's paths do not match the repository's | set `coverage.pathPrefixes`, or generate the report from the repository root |
| the gate fails on old issues | an overall condition on a branch or MR (these are ignored there), or new lines really touch old code | open the failed condition in the UI. The issue list filters by "new code" |
| `NEW_CODE_DEFINITION_FALLBACK` | `previous_version` is set, but no analysis has a version label | set `project.version` (for example `${CI_COMMIT_TAG}`) |
| marking a false positive does not change the verdict | the change applies to the branch's latest analysis | reload. The re-evaluation runs within seconds |

## Merge request and pull request comments

Test the connection first: **Settings → GitLab** (or **GitHub**) has a test action per project
mapping. Its message says what is wrong.

| Symptom | Cause |
|---|---|
| nothing on GitLab | no connection or mapping, or the analysis did not come from GitLab CI (local scans are never decorated) |
| "This GitLab is on an internal address" | the operator must list the host in `QUALOR_SCM_INTERNAL_HOSTS` |
| "GitLab refused the token" | the token lacks the `api` scope or the Developer role, or it expired |
| "The stored token can no longer be read" | `QUALOR_SECRET_KEY` changed. Enter the token again |
| comments have no links | `QUALOR_PUBLIC_URL` is not set |
| no inline discussion for an issue | it is not on an added line of the diff, or it is past the 50 per MR. The summary counts both |
| GitHub: `not_installed` / `permission_missing` | install the App on the repository, and grant Checks and Pull requests write |
| GitHub: no annotations | the workflow checked out the merge commit. Use `ref: ${{ github.event.pull_request.head.sha }}` |
| GitHub: Re-run does nothing | the webhook is inactive, the secret differs, or `QUALOR_PUBLIC_URL` is not set |

## The server

| Symptom | Fix |
|---|---|
| `required variable … is missing a value` | set `QUALOR_VERSION`, `QUALOR_SECRET_KEY` and `QUALOR_BOOTSTRAP_ADMIN_PASSWORD` in `.env`, next to `compose.yml` |
| `manifest unknown` / `pull access denied` | a mistyped tag or image name, or no access to Docker Hub. Check `QUALOR_VERSION`, and use your own registry copy behind a firewall (`QUALOR_IMAGE_PREFIX`) |
| Docker Hub `toomanyrequests` | the anonymous pull limit: `docker login`, or copy the images into your own registry |
| `another Qualor server (host …) is using /var/lib/qualor` | two containers share the data volume. Stop the other one; after a crash, start again after 30 s |
| `the data directory holds a PostgreSQL <n> cluster and this image carries PostgreSQL <m>` | the volume was made by a release with another PostgreSQL major version. Follow that release's upgrade notes (back up with the old image, restore with the new one) |
| `/readyz` answers 503 | migrations are still running, or the database is unreachable: `docker compose logs server` |
| sign-in fails behind a proxy, or every user shares one rate limit | set `QUALOR_TRUST_PROXY` |
| users signed out after a restart | `QUALOR_SECRET_KEY` changed |
| the server runs out of memory on large reports | give it 4 GiB and keep `QUALOR_WORKER_CONCURRENCY=1` |

Still stuck? Open an issue on [GitHub](https://github.com/qualor-dev/qualor/issues). Include the CLI
version (`qualor version`), the exit code and the debug log, with tokens removed.