Chat and detection problems
What a user sees when content is rejected, masked or flagged, when their AI access is locked, when a role refuses the chat, and when a file will not go through.
- Version: 0.4
- Role: admin_user, normal_user
- Type: troubleshooting
This is the page to hand a user who says "the AI stopped working for me". Almost every case is one of five, and they are distinguishable from the response alone before anyone opens a dashboard.
Tell the five apart first
| What the user sees | What it is | Runbook |
|---|---|---|
| Names or numbers replaced by placeholders; the message refused with a filter named | A content decision | Content was rejected, masked or flagged |
| The stream ends at once with a lock notice | The user's AI access is locked | A user's AI access is locked |
| "This role does not use the chat" | A role refusal, by design | A role or licence refuses the action |
| An error naming a provider, a model or a credential | Nothing reached the model | The request never reached the model |
| A file is refused, or its analysis fails | An upload limit or a service | A file will not go through |
| "Please try again later" | A rate limit | Rate limits |
Content decisions
Content was rejected, masked or flagged
A decision is not an error: the request was read, something matched, and the configured action was applied. Every decision writes a detection naming the engine, the label, the confidence and the action — so the conversation should start from that record, not from the user's paraphrase.
When several rules match one message the strongest action wins: REJECT over MASK over FLAG. A rule set to FLAG therefore still produces a rejection when something else on the same message rejects.
Note
Masking can be reversible: where PII_MAP_ENCRYPTION_KEY is set, the per-session map is stored
as AES-256-GCM ciphertext and the response is unmasked for the user. Unset, maps are never
written and responses stay masked — which is a configuration choice, not a fault.
A message is rejected, masked or flagged by the content policy
You might see: Document rejected due to policy violation · my prompt came back with names replaced by placeholders · the assistant refused an ordinary business question · a customer record was masked and the model could not answer
Before you start
Checks
- 1
Find the request in the detection record
expected · One detection explains the outcome.
- 2
Read which engine produced the match
expected · You can name the engine.
- 3
Read the filter that fired and the action it carries
expected · The action matches what the user experienced.
- 4
Decide whether the match was correct
expected · The matched span really is what the rule is for.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | no-detection-recorded (warning) | Every policy decision writes a detection. A refusal with no detection behind it did not come from the content policy — it came from the provider, from a role or licence refusal, or from the model's own refusal to answer. Fix: Read the response body and the event log entry for that request. A model refusing on its own terms is not a CID decision and no CID setting changes it. Verify: expected · Either a detection appears, or the refusal is attributable elsewhere. |
| check 2 fails | cannot-attribute-engine (info) | PII, toxicity and injection detection run in parallel and each has its own thresholds. Tuning the wrong one changes nothing and weakens a control you did not mean to touch. Fix: Use the detection's engine and label to decide where to look: PII detection for entities, the content-safety pipeline for toxicity and injection. Verify: expected · You can name the engine and its label. |
| check 3 fails | unexpected-action-priority (info) | When several rules match one message, the strongest action wins: REJECT over MASK over FLAG. A rule set to FLAG therefore still produces a rejection when something else on the same message rejects. Fix: Read every detection on that request, not only the first. Change the rule that carries the strongest action. See Filters. Verify: expected · The action matches the rule you changed. |
| check 4 fails | false-positive (warning) | A regex written for one format matches other text that shares its shape, and a model score just above its block threshold produces a confident-looking block on ordinary text. Both are tuning problems, not faults. Fix: For a pattern, narrow it and re-test it on the Filter Testing page before saving — a pattern that fails to compile protects nothing and the filter list marks it broken. For a model label, the block thresholds are properties of the shipped model image rather than runtime settings, so the remedy is a scoped filter override rather than a threshold edit. Verify: expected · The text passes, and the cases the rule exists for still fail. If that does not apply: Attach the detection id and the exact input text if policy allows it to leave the estate. Where it may not, attach the detection id alone — the record holds the matched span. |
Locks
A user's AI access is locked
Repeated jailbreak and prompt-injection detections fill a sliding window, which opens one
per-user review by the risk analyst. A lock verdict from that review ends the user's chat turns
immediately with a user_locked event.
Two properties matter when handling the user's complaint. A verdict the analyst could not tie back to specific detections is downgraded from a lock to a warning, so a lock that survived has evidence behind it. And the unlock path is deliberately narrow: one pending request per lock, three requests an hour, and a decided request is final.
A user's AI access is locked and they cannot chat
You might see: user_locked · the chat stream ends immediately with a lock notice · Your AI access is not locked · An unlock request is already pending for this lock
Before you start
Checks
- 1
Read whether the user actually holds an active lock
expected · One active lock exists for that user.
- 2
Read the review that produced the lock
expected · The review explains the lock in terms of specific detections.
- 3
For a user who cannot file an unlock request, read the state of the existing one
expected · Either no request is pending, or the pending one is waiting for a reviewer.
- 4
After an unlock, confirm the lock is gone rather than only appearing gone
expected · The message goes through.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | no-active-lock (info) | Unlock requests attach to a lock. With no active lock there is nothing to request, and the gateway says so rather than creating an orphan request — a request without its lock would be a user everyone believes is locked and who is not. Fix: Have the user retry the chat. If it still fails, the refusal is not a lock: read the response body for a role, licence or policy decision instead. Verify: expected · The message goes through. |
| check 2 fails | verdict-without-evidence (warning) | The analyst must tie every claim in its reasoning back to a detection or a tool result. Where it cannot, a lock verdict is downgraded to a warning rather than enforced — so a lock you cannot explain from the review is worth reopening rather than defending. Fix: Open the review, read its failed checks, and decide the unlock request on the evidence. A review whose own checks failed is marked unverified on the analyses list. Verify: expected · The decision is recorded against the review. |
| check 3 fails | request-already-filed (info) | One pending request per lock, enforced both by a partial unique index and by the API, and three requests per hour per user. A decided request is final; the user files a new one only after a new lock. Fix: Decide the pending request. A denial leaves the lock in place and the user may send another request; approving it clears the lock. Verify: expected · No request is left pending. |
| check 4 fails | lock-cache-not-expired (info) | The active lock is read through a short-lived Redis cache so that every chat turn does not query Postgres. A lock cleared in the dashboard can therefore still refuse for the length of that cache window. Fix: Wait out the cache window and try one more message before investigating further. Verify: expected · The message goes through. If that does not apply: Attach the lock id, the review id and the support bundle. |
Note
A lock cleared in the dashboard can still refuse the very next turn: the active lock is read through a short Redis cache. Have the user wait and send one more message before escalating.
Refusals
A role or licence refuses the action
ROLE_NOT_FOR_CHAT is not a fault — an auditor reviews the estate rather than adding traffic to
it. READ_ONLY_ROLE is the demo account working as designed: it reads everything and mutates
nothing. Both are rendered through ordinary error surfaces, which is what makes them look
broken.
The product refuses an action with 402, 403 or 423
You might see: READ_ONLY_ROLE · This is a read-only demo account (viewer role) — actions and changes are disabled. · ROLE_NOT_FOR_CHAT · FEATURE_NOT_LICENSED
Checks
- 1
Read the machine-readable code in the error body, not the sentence
expected · You can name the code.
- 2
For a 423, check whether first-boot setup ever completed
diagnostics · appliance.setupComplete = true
- 3
For a 402, read the licence state
diagnostics · appliance.license.state = "active|trial"
- 4
For FEATURE_NOT_LICENSED, read which tier is installed and whether the feature is in it
diagnostics · appliance.license.tier
- 5
For a 403 naming a role, read what the account's role may do
expected · The role holds the page or capability the action needs.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | no-code-in-body (info) | A refusal with no code did not come from the licence guard, the setup guard or the role layer. Something else produced it — most often the provider, or a validation error on the request body. Fix: Read the HTTP status and the response body, and find the request in Event Logs. Verify: expected · You can attribute the refusal. |
| check 2 fails | setup-not-complete (critical) | Until first-boot setup completes, every product route answers 423 with SETUP_REQUIRED. Administration stays reachable so the wizard can be finished without a rescue procedure.Fix: Follow The wizard finishes and the appliance asks for setup again. Verify: diagnostics · appliance.setupComplete = true |
| check 3 fails | licence-not-valid (critical) | With enforcement on and no valid licence, product routes answer 402 LICENSE_EXPIRED while administration stays available. An expired licence and a licence that never verified produce the same status and different fixes.Fix: Follow The licence has expired and the product is locked, and check the host clock before requesting a renewal. Verify: diagnostics · appliance.license.state = "active|trial" |
| check 4 fails | feature-not-in-tier (warning) | Licence tiers gate whole modules. The guard refuses the endpoint and the dashboard hides or disables the page, so the symptom is a feature that visibly exists and never works rather than one that is absent. Fix: Compare the installed tier against the generated tier table and ask your CID representative to add the feature. See Licensing. Verify: expected · It is accepted. |
| check 5 fails | role-lacks-access (info) | Role refusals are deliberate and are not licence problems. The viewer role in particular is read-only by design: it passes role checks on GET, HEAD and OPTIONS and is refused globally on everything that mutates, including downloading signed licence material. The capability model is computed in shadow mode and enforces nothing yet — what enforces today is the page matrix and the fixed roles.Fix: Give the account a role that holds the access, or perform the action as an administrator. See Access control. Verify: expected · It is accepted. If that does not apply: Include the code, the HTTP status, the route and the account's role. |
Nothing reached the model
The request never reached the model
An error naming a provider, a model or a credential means the gateway never got as far as sending anything. No detection is written, because nothing was decided.
A chat request fails before the model is reached
You might see: Provider 'openai' not found · Model 'gpt-4o' not found · Model 'gpt-4o' is not active · Model not found for provider
Before you start
Checks
- 1
Check that the provider and model catalogue is populated at all
expected · Providers and models are listed.
- 2
Read whether the model the caller named exists and is active
expected · The model is listed and active.
- 3
Check that a credential resolves for this caller and provider
expected · One active credential resolves.
- 4
Test the credential that resolves
expected · The test succeeds.
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | catalogue-empty (critical) | Providers and models are seeded data, not code. With no rows, no model id can resolve and every completion fails at the first lookup. Fix: Follow No providers are in the catalogue. Verify: expected · Providers and models are listed. |
| check 2 fails | model-inactive-or-absent (warning) | The gateway resolves the model row before it resolves a credential, and it refuses an inactive model rather than silently substituting one. A model deactivated in the catalogue therefore breaks every client still naming it, including clients that worked yesterday. Fix: Re-activate the model under Models, or change the client to a model that is active. A client that hard-codes a model id needs that id to stay active. Verify: expected · The completion starts streaming. |
| check 3 fails | no-credential-resolves (critical) | Credentials resolve tenant-first and fall back to the tenant's group. A tenant in no group, or a group whose credential was deactivated, resolves to nothing and the request fails before any provider call is made. Fix: Add an active credential for that provider on the tenant, or add the tenant to a group that already holds one. See Credentials. Verify: expected · The completion starts streaming. |
| check 4 fails | credential-rejected (critical) | A credential resolves and the provider refuses it, so the failure surfaces on the chat path rather than on the credential page where it was configured. Fix: Follow A provider credential will not save, or its test fails. Verify: expected · Both succeed. If that does not apply: Attach the support bundle and the request's model id, tenant and timestamp. |
Files
A file will not go through
Three separate limits refuse a file: the format (macro-enabled spreadsheets cannot be safely redacted and are refused rather than partially handled), the size, and the document parser being disabled or behind its circuit breaker.
A chunked or resumable upload is refused rather than passed through, because no path buffers across requests and an uninspected upload is the thing inspection exists to prevent.
A file is refused, or its analysis fails
You might see: No file uploaded (expected multipart field "file") · Image size exceeds maximum allowed size of 10MB · Unsupported file type. Upload a PDF, DOCX or TXT. · legacy and macro-enabled spreadsheet formats cannot be safely redacted
Before you start
Checks
- 1
Read whether the format is one the product accepts at all
expected · The format is accepted on that surface.
- 2
Read the size limit for that surface
expected · The file is under the limit named in the message.
- 3
Check whether the client was doing a chunked or resumable upload
diagnostics · posture.resumableUploadPolicy = "block"
- 4
Read whether document analysis is switched on for this deployment
expected · The service is enabled.
- 5
Read the document parser's own health
diagnostics · services[document-parser].healthy = true
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | format-not-accepted (info) | Some formats are refused on purpose rather than partially handled. A macro-enabled spreadsheet cannot be redacted safely, and a redaction that silently missed content would be worse than a refusal. Fix: Save the file in an accepted format — a spreadsheet as .xlsx without macros, a document as PDF, DOCX or TXT — and upload it again.Verify: expected · It is accepted. |
| check 2 fails | over-size-limit (info) | Each surface caps what it will buffer, because analysis holds the whole file in memory and an unbounded upload is an out-of-memory kill for whichever detection service is largest. Fix: Split or compress the file, or use the document path rather than the image path for large scans. Verify: expected · It is accepted. |
| no check points here | resumable-refused-by-policy (warning) | No path buffers across requests, so a chunked or resumable upload cannot be inspected. Rather than passing it through uninspected, all three paths refuse it by default and the extension records a visible "not inspected" event. Reassembly — buffer by upload id, inspect at finalize, re-fragment — is not implemented. Fix: Have the user upload the file in a single request, or accept the risk explicitly by changing the policy. Do not read a refusal here as a product fault: it is the fail-closed choice working. Verify: diagnostics · posture.resumableUploadPolicy = "block" |
| check 4 fails | parser-disabled (warning) | Document analysis is a separate service and can be switched off, for instance on a minimal development stack. The endpoint answers 503 with a distinct message so it is not confused with a service that is enabled and down. Fix: Enable the document parser and restart the gateway so it re-reads the setting. See File redaction. Verify: expected · The analysis runs. |
| check 5 fails | parser-unhealthy (critical) | The gateway holds a circuit breaker in front of the parser: once it has failed repeatedly the gateway stops calling it and answers "temporarily unavailable" immediately, which is why the failure can look instant. Fix: Follow One service is unreachable or reports itself unhealthy. Verify: diagnostics · services[document-parser].healthy = true If that does not apply: Attach the support bundle and the file's type and size. Do not attach the document itself unless policy allows it to leave the estate. |
What to collect before escalating
- The detection id from All Detections, not the text of the message — the record already holds the matched span, and the text may not leave the estate.
- The exact response the user saw, including any
code. - The time and the user, so the gateway log and the event log can be correlated.
Related
- All detections — the record every decision writes.
- Filters — the rules and their actions.
- The content-safety pipeline — what runs, in what order.
- LLM review — reviews, verdicts and unlock requests.
Last updated on