Troubleshooting
Find the message or symptom you see, then its cause and fix. For a scan, start with two commands:
qualor validate # the resolved configuration, secrets redacted
QUALOR_LOG_LEVEL=debug qualor scan # every analyzer's command line, output and skip reason
The scanner’s exit codes are in the CLI reference.
The scan
| Message or symptom | Cause | Fix |
|---|---|---|
exit 2, no project key |
no CI project path is detected outside GitLab and GitHub | set QUALOR_PROJECT_KEY, --project-key or project.key |
exit 2, server.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, invalid qualor.yml and analyzers: Unrecognized key: "eslnt" |
a typo in qualor.yml: unknown keys are errors |
fix the key. The JSON Schema helps in the editor |
exit 2, invalid qualor.yml naming token |
secrets may not be in the config | remove it and use QUALOR_TOKEN |
exit 2, registry configs download rules over the network |
a registry id (p/…) in semgrep.configs |
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 or timeout | the runner cannot reach the server | check QUALOR_URL, the firewall, and HTTPS_PROXY and 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 or upload rejected |
the report is larger than the server or the 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. If analyses are just slow, raise gate.timeoutSeconds |
exit 4, PROJECT_NOT_FOUND |
no project has 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 |
this scanner is 0.3.2 and the server is 0.4.0 |
the scanner image in CI is from another release than the server | pin the scanner to the tag the message names |
AI review: no snapshot sent, … |
the AI review snapshot was not uploaded, for the reason the message gives. The scan result is not affected | see AI review: Troubleshooting |
the repository has no ESLint configuration |
no ESLint config at the repository root | add eslint.config.js, or set analyzers.eslint.configFile |
ESLint fails, Cannot find package … |
the dependencies are not installed | run npm ci, or your package manager’s equivalent, before the scan |
… is a legacy eslintrc configuration, which ESLint 9 does not read |
ESLint 9 and later read only flat configs | migrate to eslint.config.js |
SpotBugs: no compiled classes in … |
the project was not built | build before the scan, or set classDirs |
the qualor/scanner image ships no Semgrep rules yet |
no Semgrep rules are bundled | name your rule files in analyzers.semgrep.configs, or ignore the message |
SEMGREP_CONFIG_MISSING |
a file in analyzers.semgrep.configs does not exist. The other files ran, and an AI review snapshot is not sent |
fix the path, or remove the entry |
Qualor's security rules are not installed |
the scan does not run in a qualor/scanner image with the rules. Images of earlier Qualor releases have none |
use a current qualor/scanner image, or unpack a rules release and set QUALOR_RULES_DIR to it. Or ignore the message |
QUALOR_RULES_DIR must be an absolute path outside the repository |
QUALOR_RULES_DIR is relative, or points into the checkout |
set it to an absolute path outside the checkout, or unset it |
QUALOR_RULES_DIR does not exist |
the directory it names is missing | create or unpack the rules there, or unset QUALOR_RULES_DIR |
the rules pack … does not match its manifest checksum, or another rules pack reason |
QUALOR_RULES_DIR holds a changed or incomplete copy of the rules |
unpack the rules again, or unset QUALOR_RULES_DIR |
LICENSE_SCAN_TIMEOUT or LICENSE_SCAN_FAILED |
Trivy’s licence pass ran longer than analyzers.trivy.licenseTimeoutSeconds, or failed. The vulnerability findings are not affected |
raise the timeout, or read the scan log (Dependency licences) |
| Dependencies tab: most packages have an Unknown licence | pnpm, Yarn, Go, NuGet, Maven and Gradle licences are read from the installed packages | install the dependencies in the scan job, before qualor scan (Dependency licences) |
VULNERABILITY_DB_STALE |
Trivy’s database is more than 14 days old | a scanner talking to a 0.15 or later server gets the server’s database, so look at the server’s database state (The vulnerability database). With an older server, or the scanner setting off, move to a newer scanner release |
VULNERABILITY_DB_FETCH_FAILED |
the scanner could not download the database from the server. The scan went on with the cached copy or the image’s database, as the message says | look at the reason in the message. A 503 means the server was busy: run again, or raise QUALOR_VULNDB_MAX_DOWNLOADS. Check that the token and the server address are right |
VULNERABILITY_DB_CACHE_UNTRUSTED |
the server gave no database (it is older than 0.15, its scanner setting is off, or the scan ran with --dry-run), and QUALOR_TRIVY_CACHE_DIR is inside the checkout with a database of its own in db/. The scanner never uses a database there unchecked, so the scan used the image’s |
to use your own database, keep it in a directory outside the checkout that jobs running repository code cannot write, and point QUALOR_TRIVY_CACHE_DIR there. Otherwise remove db/ from the directory |
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 |
TAINT_ENGINE_FALLBACK |
Qualor’s Roslyn taint engine gave no complete result, so the OpenGrep C# rules ran instead. The message gives the reason, the log the detail | see the reasons below |
TAINT_ENGINE_PROJECT_ERRORS |
the engine analysed C# projects with load or compile errors, for example unresolved packages. Their findings may be incomplete | restore the packages and build without errors. The log names each project and its first error |
NESTED_REPOSITORY_SKIPPED |
a submodule or nested clone | none: this is expected. Scan that repository on its own |
| many issues in generated code | generated files are scanned | add them to sources.exclude |
The reasons of TAINT_ENGINE_FALLBACK (C#):
| Reason | Fix |
|---|---|
budget |
raise QUALOR_TAINT_TIME_LIMIT (seconds) or QUALOR_TAINT_MEMORY_LIMIT (megabytes), or give the job more memory. The log line taint engine memory limit shows the value in use. The defaults are in the configuration reference |
load |
the sources of a project could not all be loaded. The log names the project: check its build output and the paths under the checkout |
no-inputs |
build between qualor dotnet begin and end, with --no-incremental |
dotnet-missing |
use the qualor/scanner-dotnet image |
no-engine, engine-mismatch |
use a current image, or unpack the rules release again |
crash |
report it, with the log |
The gate
| Symptom | Cause | Fix |
|---|---|---|
gate error, “new code unavailable” |
a shallow clone, and the baseline could not be fetched | GIT_DEPTH: 0 (GitLab), fetch-depth: 0 (GitHub), git fetch --unshallow elsewhere |
gate error on the main branch, “<sha> could not be fetched from origin”, with the full history |
the project holds analyses of another git history (the repository was re-created, or the project key reused), so the baseline commit is not in this repository. A 30-day new-code period does not help: its baseline is still an old-history analysis | set the new-code definition to an analysis of the new history ({ "type": "analysis", "analysisId": "…" }), or use a new project |
| every merge request 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 |
| a 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 tested files | 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, which counts on the main branch only, or new lines that 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 main-branch analysis has another version label |
set project.version from your build’s version on the main branch |
| 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 → Repositories has a Check for each project’s mapping, and its message says what is wrong.
| Symptom | Cause and fix |
|---|---|
| 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 is wrong, revoked or expired. Enter a new one |
| “The token lacks a permission” | the token lacks the api scope or the Maintainer role. Developer is enough for comments, but not for a commit status on a protected branch |
| “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 or review comment for an issue | the issue is not on an added line of the diff, or it is past the 50 per merge or pull request. The summary counts both |
GitHub: not_installed or permission_missing |
install the App on the repository, with Checks, Contents and Pull requests write (GitHub: Troubleshooting) |
| GitHub: no inline comments for the analyzers’ issues | the workflow checked out the merge commit. Use ref: ${{ github.event.pull_request.head.sha }}. AI review’s comments are posted either way |
| no AI review section in the summary | AI review is not on for the project (Why AI review is off for a project), or the analysis is not of a merge request |
| the AI review section says the review did not run or failed | see AI review: What the summary says |
| GitHub: Re-run does nothing | the webhook is inactive, its secret differs, or QUALOR_PUBLIC_URL is not set |
a reply to an AI review comment, or a qualor review note, is answered late or never |
see AI review: Why a reply or a command was not answered. A webhook makes the answer immediate. Without one, it comes at the next analysis (GitLab, GitHub) |
| Slack or Teams: a webhook is refused, a message does not arrive, or there is no digest | see Slack and Microsoft Teams: Troubleshooting |
The AI assistant
Every message and error code of the AI assistant, and automatic fix suggestions, are in AI assistant: Troubleshooting. Two symptoms show no message:
| Symptom | Fix |
|---|---|
| no AI assistant panel in the issue view | an instance admin connects a provider and allows the organisation to use AI in Settings → AI provider |
| an action is missing from the panel | its feature is off for the organisation, or the issue cannot be sent: a secret rule, a credentials file, an excluded path or project. Triage and fix need an open issue |
The server
To send us the state of a server, download a support bundle (Business and Enterprise).
| 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 or pull access denied |
a mistyped tag or image name, or no access to Docker Hub. Check QUALOR_VERSION. Behind a firewall, use your own registry copy (QUALOR_IMAGE_PREFIX) |
Docker Hub toomanyrequests |
the anonymous pull limit: run 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. Read docker compose logs server |
| sign-in fails behind a proxy, or every user shares one rate limit | set QUALOR_TRUST_PROXY |
| users are signed out after a restart | QUALOR_SECRET_KEY changed |
the server stops at start, naming QUALOR_SMTP_URL or QUALOR_SMTP_FROM |
the URL is not smtp://… or smtps://… as Email shows (URL-encode @, : and / in the user name and password), or QUALOR_SMTP_FROM is missing |
| Send test fails, or Recent messages lists an error | see Email: Troubleshooting |
| the server runs out of memory on large reports | give it 4 GiB, and keep QUALOR_WORKER_CONCURRENCY=1 |
The licence
The server logs a key it refuses as licence key rejected, with a reason:
reason |
Cause | Fix |
|---|---|---|
malformed |
not a Qualor key, or cut short when it was copied | copy the whole key again. Line breaks and spaces do not matter |
unknown-key |
the key was signed with a key this version of Qualor does not know: it is for a newer version, or it was altered | upgrade the server, or ask us for a new key (Enterprise) |
bad-signature or bad-payload |
the key was changed after it was issued | copy the key again from the original message, or ask for a new one |
revoked |
this licence was withdrawn | ask for a new key |
not-yet-valid |
the key’s issue date is more than a day ahead of the server’s clock | check the server’s clock (NTP) |
| Message or symptom | Cause | Fix |
|---|---|---|
qualor-server: QUALOR_LICENSE_FILE: cannot read …, and the server does not start |
the file is not there (the secret is not mounted), or user 65532 cannot read it | fix the secret mount or the file’s permissions, or unset QUALOR_LICENSE_FILE |
qualor-server: QUALOR_LICENSE_FILE: … is not a file |
the path names a directory or a device, often a secret mounted as a directory | point the variable at the file inside it |
qualor-server: QUALOR_LICENSE_FILE: … is larger than 16 KiB |
the path names some other file | point the variable at the file that holds only the key |
qualor-server: QUALOR_LICENSE_FILE: … is not a text file in UTF-8 (or UTF-16 with a byte-order mark); save it as UTF-8 |
the file is in another encoding, such as UTF-16 without a byte-order mark | save the key as UTF-8 (in PowerShell: Set-Content -Encoding utf8) |
set QUALOR_LICENSE or QUALOR_LICENSE_FILE, not both |
both variables are set | keep one |
| Settings → Licence says Restart required. | a key was saved or removed in the web UI. It applies at the next start | restart the server |
| the licence page has no field to paste a key | the key comes from QUALOR_LICENSE or QUALOR_LICENSE_FILE (LICENSE_MANAGED_BY_ENVIRONMENT in the API) |
change the variable and restart |
Roles and the audit log
| Message or symptom | Cause | Fix |
|---|---|---|
PROJECT_GRANT_LIMIT_REACHED (409) |
the project already has 1 000 roles granted directly on it | remove a grant you no longer need, or give the role at organisation level |
a bulk status change answers 200, but lists some issues in failed with code: "FORBIDDEN" |
the caller’s role may read those issues but not triage them (a Viewer). The other issues in the request changed | ask an org admin for the right role, then retry the refused ones |
LAST_ADMIN (409) changing a member’s role or removing a member |
the member is the organisation’s last Organization admin, and an organisation always keeps one | make someone else Organization admin first |
FORBIDDEN (403) setting scmConnectionId or scmProjectRef on a project |
mapping a project to a GitLab or GitHub repository needs an org admin, even for a Project admin | ask an org admin to set the mapping (Settings → Repositories, or the same PATCH) |
a status change answers CONCURRENCY_CONFLICT (503) with Retry-After |
another change to the same issue, or the audit log’s recording of it, ran at the same moment. Nothing was written | wait as long as Retry-After says, and send the request again |
| “The chain breaks at event N” (Settings → Audit log → Verify chain) | event N’s content, or its link to the event before it, no longer matches its stored hash. A row was changed, a row was deleted outside retention, or the retention anchor was changed | compare event N with an earlier export or your SIEM receiver’s copy. If they agree with what came before it, the database changed since: ask whoever administers it |
AUDIT_CHAIN_ANCHOR_MALFORMED (409) on changes, or sign-in refused with it |
the stored anchor of the audit chain was changed and no longer parses | see Repair the audit-chain anchor |
| the SIEM stream stopped after a licence change: Waiting to be sent grows, and nothing fails | the key no longer lists audit-log.stream (a Business key replaced an Enterprise one). The stream is paused, its settings and position kept |
apply an Enterprise key and restart: the stream catches up from where it stopped. To send nothing more, press Remove under Stream status (Streaming to a SIEM) |
FEATURE_NOT_LICENSED (403) naming audit-log.stream |
the request changed the stream (a stream object in PUT /api/v0/ee/audit/settings, POST /api/v0/ee/audit/settings/stream/test or POST /api/v0/ee/audit/settings/stream/regenerate-secret), and the key lists audit-log without audit-log.stream, which needs the Enterprise plan. A request that also changed retention saved nothing |
save retention on its own. To use the stream, apply an Enterprise key |
| the SIEM stream keeps failing | the receiver is down or refuses the connection, or its address or certificate changed | read Last error and Failing since in Settings → Audit settings, and fix the receiver. Events wait and are retried until retention removes them (Streaming to a SIEM) |
| Export JSON Lines does nothing, or the browser reports a failed download, with no message on the page | the button is a plain download link, so the page never shows why a download was refused | 429: another of your exports is still running; wait for it, or its download, to finish. 403: the licence lapsed (audit-log is no longer active); renew it. 409: the stored chain anchor is damaged; an instance admin must repair it. The audit settings and chain head cards show this error too |
Repair the audit-chain anchor
AUDIT_CHAIN_ANCHOR_MALFORMED means the audit-chain row of the table instance_settings was
changed and no longer parses. The row records where retention last cut the chain. Qualor refuses
to guess, so it records nothing:
- Every change it would record answers 409.
- While the audit table is empty, even signing in is refused. Personal tokens and sessions that are already signed in keep working.
- Retention stops removing events until the row is repaired.
- Removing access is never blocked. Signing out, revoking a personal or project token, deactivating
a user, removing a member or a project role, and demoting a role still work. Their audit event is
not written. Instead, the server log gets one error line,
audit event skipped: the audit-chain anchor is malformed, naming the action and the ids of who did it and to whom.
An instance admin who can write the database restores the row:
UPDATE instance_settings
SET value = '{"throughSeq":"<seq>","throughHash":"<64 hex characters>","prunedAt":"<ISO 8601 time>"}'::jsonb,
updated_at = now()
WHERE key = 'audit-chain';
Take the values from a database backup. Without one, take them from the newest audit.pruned event
in an export or your SIEM receiver’s copy: details.throughSeq, details.throughHash, and its
occurredAt as prunedAt. Then run Verify chain. Keep the server log’s audit event skipped
lines: they are the only record of the access removed meanwhile.
Single sign-on and SCIM
A failed single sign-on ends on the sign-in page, or on Settings → Linked accounts for a link,
with a fixed message. The address ends in ?sso_error=<code>. A code the page does not know shows
“Single sign-on failed. Try again, or ask an administrator.”
The person never sees the precise reason. The server log has it, in a warn line
single sign-on failed with component: "sso", the connectionId, the reason (the code below)
and a detail, such as oidc.id_token.aud, oidc.discovery, saml.recipient or
saml.precheck.signature_shape. With the audit log licensed, each failure on an existing
connection is also recorded as sso.sign_in_failed. A request naming a connection that does not
exist is only logged.
sso_error code |
What the person sees | Cause | Fix |
|---|---|---|---|
unavailable |
This sign-in method is not available. | the connection is unknown, disabled or deleted, or not in effect on the Business plan (log detail oidc.not_in_effect or saml.not_in_effect, see below). Or its sign-in could not start: the IdP’s discovery document could not be read, or the stored configuration is not valid |
enable the connection, and press Test in Settings → Single sign-on. The log’s detail says which step failed |
flow_expired |
The sign-in took too long or was already used. Try again. | more than 10 minutes passed at the IdP, the page was reloaded or opened from the browser history, or the answer was already used | start again from Qualor’s sign-in page. A SAML sign-in started from the IdP’s app launcher (IdP-initiated) is not supported |
flow_mismatch |
The sign-in was started in another tab or browser. Start again here. | the sign-in finished in another browser or tab than the latest one that started it, or the browser blocks Qualor’s cookies | start again, in one tab. Allow cookies for Qualor |
idp_error |
Your identity provider did not complete the sign-in. Try again, or ask an administrator. | the IdP answered with an error: the person cancelled, is not assigned to the application, or the IdP refused the request | check the IdP’s own sign-in log. Assign the person to the application |
invalid_response |
Qualor could not accept the answer of your identity provider. Ask an administrator to check the connection. | the answer failed one of the checks listed below the table | read the log’s detail, then fix the connection or the IdP |
replayed |
This sign-in was already used. Start again. | the same SAML assertion arrived twice | start again. If it keeps happening, something between the IdP and Qualor resends responses |
required_claim |
Your identity provider account is not allowed to sign in to Qualor. Ask an administrator. | a required claim of the connection, such as Google’s hd, is missing or has another value |
expected for accounts outside your domain. Otherwise, check the required claims |
no_account |
There is no Qualor account for you yet. Ask an administrator. | no account is linked, and the connection does not create accounts at the first sign-in | create the account and let the person link it, turn on linking by verified email, or let the connection create accounts |
inactive_user |
Your Qualor account is deactivated. | the linked Qualor account is deactivated | reactivate it in Settings → Users, or at the IdP for a SCIM user |
email_in_use |
Your identity provider’s email already has a Qualor account. … | a new account would take a verified email that another Qualor user already has | the person signs in to that account with its password and links it under Settings → Linked accounts. Or an admin turns on linking by verified email, which never links instance admins |
username_unavailable |
A Qualor account could not be created for you because your user name is already taken. … | the user name and its variants -2 to -20 are all taken |
rename one of the existing accounts, or send another username claim |
identity_in_use |
This identity provider account is already linked to another Qualor account. | a link: the IdP account belongs to another Qualor user | an instance admin unlinks it from that user first |
already_linked |
Your Qualor account is already linked to an account of this identity provider. | a link: this user already has a link on this connection | unlink the old one first, under Settings → Linked accounts |
rate_limited |
Too many sign-in attempts. Wait a minute and try again. | more than 30 starts a minute from one address, or more than 60 OIDC callbacks, SAML answers or finish steps | wait a minute. Many people behind one proxy share an address: set QUALOR_TRUST_PROXY |
invalid_response means the IdP’s answer failed one of these checks:
- The issuer, audience, redirect URI, ACS URL or certificate is wrong.
- The token has expired, or is dated in the future: check the clocks.
- The ID token is signed with a key the JWKS lacks, with
HS256, or with an RSA key under 2048 bits. - The SAML response is signed with SHA-1 (see the SHA-1 row below), or holds comments or a DOCTYPE.
- The assertion is not signed.
- It has more than 1 000 groups: send only the groups Qualor maps.
- The SAML sign-in is larger than 16 KiB.
| Message or symptom | Cause | Fix |
|---|---|---|
| nobody can sign in: the IdP is down or the connection is broken | password sign-in is limited to break-glass admins, and none is at hand | set QUALOR_FORCE_PASSWORD_SIGN_IN=true and restart. Sign in with a password and fix the connection. Then remove the variable and restart (If your identity provider is down) |
the server does not start: QUALOR_FORCE_PASSWORD_SIGN_IN must be true or false |
another value, such as yes or 1 |
use true or false, or leave it empty |
PUBLIC_URL_REQUIRED (409) enabling a connection |
QUALOR_PUBLIC_URL is not set. The connection page shows no values to copy either |
set QUALOR_PUBLIC_URL and restart |
| Test says “The discovery document names another issuer. …” | the issuer entered differs from the one the discovery document names, even by a trailing slash. Or it is Entra ID’s common endpoint or Azure AD B2C, which are not supported |
copy issuer exactly from <issuer>/.well-known/openid-configuration. For Entra ID, use https://login.microsoftonline.com/<tenant id>/v2.0 |
| Test says “The host is not public. List it in QUALOR_SSO_INTERNAL_HOSTS if Qualor may call it.”, or “The issuer URL is not allowed. Use https, or list the host in QUALOR_SSO_INTERNAL_HOSTS.” | the IdP resolves to an internal address, or uses plain http |
add its host (and port) to QUALOR_SSO_INTERNAL_HOSTS and restart. Link-local and cloud metadata addresses are never allowed |
the IdP says its certificate or signature is SHA-1, or every SAML sign-in fails with the detail saml.precheck.signature_shape |
the IdP signs with SHA-1, which Qualor refuses | switch the IdP to SHA-256. Keycloak: Signature algorithm RSA_SHA256. Entra ID: Signing Algorithm SHA-256. Okta: RSA_SHA256. AD FS: Secure hash algorithm SHA-256 |
new SAML users get names like G-3f2a… |
no username attribute is set, so the NameID became the user name. Keycloak’s persistent NameID is opaque | send a username attribute and name it in Username claim |
| a user who signed in through SSO has no email | the IdP did not mark it verified: email_verified in OIDC, or, for SAML, This identity provider verifies the email attribute is not ticked |
none: an unverified email is never stored. Use SCIM, or tick that SAML setting if your IdP does verify emails |
| a group mapping gives nothing | the group value differs (case counts, and Entra ID sends object ids), the connection’s groups do not come from claims or SCIM, or a mapped project already has 1 000 roles granted directly on it, so sync skips that grant | compare the mapping with what the IdP sends. For a full project, remove grants you no longer need |
| a role set by hand changes back, or a removed member comes back | group sync manages the membership (“From SSO group sync” on Settings → Members) | change its role by hand to take it over from sync, or change the IdP groups or the mappings |
LAST_BREAK_GLASS_ADMIN (409) deactivating or demoting an instance admin |
password sign-in is limited to break-glass admins, and this is the last one who could still use a password. The guard follows the stored setting, even while QUALOR_FORCE_PASSWORD_SIGN_IN is set or the licence has lapsed |
add another one under Break-glass administrators in Settings → Sign-in, or first set Password sign-in back to Everyone with a password |
LAST_SIGN_IN_METHOD (409) unlinking an account |
nothing else would sign the user in: no password the current policy lets them use (the emergency switch does not count), and no link on another connection in effect | an instance admin sets a password first with Reset password on the user’s row in Settings → Users, which ends the user’s sessions and revokes their personal tokens. Then unlink. The password counts only while password sign-in is allowed for that user. A user without a password cannot set one themselves |
LAST_SSO_CONNECTION (409) disabling or deleting a connection |
password sign-in is limited to break-glass admins, and this is the last enabled connection: nobody else could sign in | first set Password sign-in back to Everyone with a password in Settings → Sign-in. On the Enterprise plan, you can enable another connection first instead. To switch identity provider on the Business plan, follow the steps |
SSO_MULTI_NOT_LICENSED (409) creating or enabling a connection: “Your plan allows one enabled single sign-on connection. …” |
the key lists sso without sso.multi (the Business plan), and another connection is already enabled |
create the connection disabled and prepare it. To switch to it, disable the enabled one first (Switch identity provider on the Business plan). Several enabled connections need the Enterprise plan |
SSO_CONNECTION_LIMIT_REACHED (409) adding a connection |
10 connections are stored, enabled or not: the most any plan allows | delete a connection you no longer need |
a provider disappeared from the sign-in page after a licence change, and its sign-ins end with unavailable (log detail oidc.not_in_effect or saml.not_in_effect) |
the new key lists sso without sso.multi (a Business key replaced an Enterprise one), so only the oldest enabled connection is in effect. Nothing was deleted |
to use another connection instead, disable the one in effect: the next oldest enabled one takes over. To use them all again, apply an Enterprise key and restart (After a move to the Business plan) |
SCIM_MANAGED_IDENTITY (409) unlinking your own account |
the IdP provisions this link through SCIM | ask an instance admin, or have the IdP unassign you |
SSO_UNAVAILABLE (503) linking an account |
the connection’s IdP cannot be used now: its discovery failed | try again later. An admin presses Test on the connection |
| signed out of the IdP, but still signed in to Qualor | Qualor does no single logout | sign out of Qualor too. Deactivating a user ends all their sessions |
SCIM
| Message or symptom | Cause | Fix |
|---|---|---|
| SCIM answers 401 | the token is missing, mistyped, revoked or expired, or its connection was deleted | create a token (New token in Settings → SCIM) and give it to the IdP |
| SCIM answers 429 | more than 1 200 requests a minute with one token. Or the request’s token is wrong or missing, and its address already sent 60 such requests this minute; a valid token from that address still works | wait for Retry-After, and fix the token the IdP sends. A 429 in the server log may come from another client at the same address with a revoked or wrong token. Behind a reverse proxy, set QUALOR_TRUST_PROXY so each client is counted by its own address |
SCIM answers 503 with Retry-After |
the request collided with another change in the database, and changed nothing | none: the IdP retries it. If it persists, check the server log |
SCIM answers 403 FEATURE_NOT_LICENSED |
the licence does not list scim, or it lapsed. SCIM is in the Enterprise plan only, not in Business. Deprovisioning has stopped |
apply an Enterprise key, or renew the licence, and restart. Meanwhile, deactivate leavers in Settings → Users |
SCIM answers 409 uniqueness creating a user |
the userName or externalId is already on the connection, or the email belongs to another Qualor account, such as one SCIM deleted earlier (its row stays) |
change the old account’s email. Or reactivate it and turn on linking by verified email, so the new SCIM user links to it |
SCIM answers 400 mutability deactivating a user |
the user is the last active instance admin, or the last break-glass admin while password sign-in is limited | make another instance admin or break-glass admin first |
SCIM answers 400 invalidFilter |
a filter other than one eq comparison on a supported attribute |
see SCIM for what is supported |
| someone an admin deactivated is active again | the IdP sent active: true over SCIM, which overrides a deactivation made in Qualor |
deactivate or unassign them at the IdP |
Still stuck? Open an issue on GitHub with the CLI
version (qualor version), the exit code and the debug log, tokens removed.