Troubleshooting
Start with two commands. They answer most questions:
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. Include the CLI
version (qualor version), the exit code and the debug log, with tokens removed.