Troubleshooting
Match the symptom to the fix below. If the symptom does not fit any section, escalate to Tiny Support with the output of docker logs ai-service --tail 200 and a redacted copy of the PROVIDERS value. In image version 1.3.0 and later, also attach an export from AI Diagnostics, which shows the prompt, response, and model of each failed AI request.
Quick triage
Work through this list to identify the symptom area:
-
Is the container running? Run
docker psto check. If the container has exited or will not pull, see Container startup failures. -
Does
/healthreturn 200? Runcurl -fsS http://localhost:8000/health. If it times out or returns 5xx, see Container startup failures. -
Does the API return an auth error? If the response is 401,
allowed:false, orinvalid-jwt-*, see API and JSON Web Token (JWT) authentication. -
Does the SSE stream carry an
event: errorfrom the LLM? If yes, see Large language model (LLM) provider errors. -
Is the editor side broken? Missing toolbar, token 401, or hanging stream? See Editor and front end.
-
Slow, timing out, or failing under load? See Performance.
-
Scaling, upgrades, or deployment questions? See Production deployment.
-
Content rejected, or a hook error? See Content checks and hooks.
-
Need the meaning of a service log message? See Log reference.
If none of the above match, see Diagnostic recipes and then escalate to Tiny Support.
Container startup failures
Run docker logs ai-service first. All entries below assume the log output is available.
| Error / symptom | Cause | Fix |
|---|---|---|
|
Key was truncated, contains a line break, or has surrounding whitespace |
Paste the key as a single unbroken line. Verify the first and last eight characters against the original. |
|
|
Switch to |
|
The MySQL user lacks the listed privileges on the AI service database. Privileges granted globally ( |
Grant the privileges on the AI service database itself ( |
|
Postgres schema not pre-created |
Run |
|
The managed PostgreSQL server requires TLS ( |
See Managed database TLS. |
|
Several replicas started at the same time against a new database and ran the schema migrations concurrently. |
The replica restarts and recovers on its own. To avoid the error, start one replica, wait until it is healthy, then scale up. See Upgrade process. |
|
|
Pin |
|
A |
Correct the |
|
A |
Start from the provider example on the LLM providers page and add fields one at a time. |
Container exits with no useful log |
Missing required env var, or malformed JSON in |
Run |
Pods stay unready on Amazon EKS Auto Mode, restart after failed liveness probes, and |
The database and Redis security groups allow inbound traffic from the node security group, but EKS Auto Mode attaches the cluster primary security group to the pods. |
Allow inbound database and Redis traffic from the cluster primary security group. See Infrastructure recommendations. |
|
Port mapping missing |
Add |
|
AI service is on a different Docker network from the data layer |
Use |
API and JSON Web Token (JWT) authentication
These assume the container is running and /health returns OK.
| Error / symptom | Cause | Fix |
|---|---|---|
|
Token signed with the wrong key. Most commonly, signed with |
Re-copy the API Secret from the Management Panel at |
|
|
Copy the Environment ID from |
|
Token is past its |
Issue tokens with a reasonable lifetime (for example |
|
Environment was not created through the Management Panel UI |
Delete and recreate the environment through |
JWT silently rejected |
Token signed with RS256 instead of HS256 |
Re-sign with |
|
Stream abort left temporary state blocking the conversation |
Start a new conversation or reload the page. Custom UIs should create a fresh conversation after cancel. |
HTTP 500 error on every endpoint |
|
Use an array of strings:
Common mistakes: |
Large language model (LLM) provider errors
Most of these appear as event: error inside the SSE stream, and the HTTP response is still 200. NoValidApiKeysFoundError is the exception: the message request fails with HTTP 500, and the cause appears in the container log.
Cloud providers (OpenAI, Anthropic, Google)
| Error | Fix |
|---|---|
|
Update the key in |
AWS Bedrock
| Error | Fix |
|---|---|
|
The provider has no credentials that the service can use. In image versions earlier than 1.2.0, inline |
|
Enable model access in Bedrock console → Model access. Attach an IAM policy with |
|
Complete the AWS Marketplace subscription for Anthropic in Bedrock console → Model access → Anthropic. |
|
Use the region-prefixed inference profile ID (for example |
Google Vertex AI
| Error | Fix |
|---|---|
|
The provider has no credentials that the service can use. In image versions earlier than 1.2.0, inline |
Auth errors with a valid service account |
|
|
Run |
Blocked by GCP org policy |
Check |
Azure OpenAI
| Error | Fix |
|---|---|
Model not found / |
|
API errors with no provider message |
Set |
OpenAI-compatible (Ollama, vLLM, LM Studio)
| Error | Fix |
|---|---|
"Not Found" in SSE error |
|
|
Start Ollama with |
"does not support tools" |
Use an official model ( |
|
Model is too slow for the default timeout. Set |
Content checks and hooks
Content moderation, guardrails, and hooks return their own error codes: unsafe-content-detected, content-rejected, hook-failed, and hook-timeout. For the meaning of each code, see Error code reference.
To find the error code of a failed request in the editor, read the TinyMCE AI Error: entry in the browser console, then find the service log entry with the same trace ID. See Tracing a failed request. Some failures show only the status code in the console. The troubleshooting section of each feature explains how to find the log entry for those.
For symptoms and fixes, see:
Editor and front end
Confirm /health is OK and a direct curl to /v1/conversations works before investigating the editor.
| Symptom | Fix |
|---|---|
No AI buttons in the toolbar |
Ensure TinyMCE 8+ is loaded, |
Token fetch returns 401 |
The token endpoint’s own authentication middleware is rejecting the request. Check session cookies, Cross-Origin Resource Sharing (CORS) credentials, and bearer tokens in the browser network tab. |
Token returned but rejected by the AI service |
See API and JSON Web Token (JWT) authentication above: wrong secret, wrong |
AI responses hang in the browser |
The reverse proxy is buffering the SSE stream. Set |
CORS error on |
Add the editor’s origin (scheme + host + port) to the |
Editor renders then disappears (Next.js / Nuxt / SvelteKit) |
TinyMCE references |
Editor shows an error, and the browser console logs |
Token endpoint is returning an invalid JWT or non-JSON response. Test with |
Performance
| Symptom | Fix |
|---|---|
Self-hosted model is slow through the AI service compared with raw |
Co-locate the inference server with the AI service. Use a smaller or more quantized model. Disable telemetry during development ( |
Containers OOM or MySQL takes 60+ seconds to start (Colima) |
Default Colima VM is too small. Run |
Log reference
Read the AI service log with docker logs ai-service, or with kubectl logs on Kubernetes. To collect the log from several instances in one place, see Distributed logging.
Log format
Error and fatal entries are JSON objects, one per line:
| Field | Content |
|---|---|
|
The severity, as a number: |
|
The time of the entry, in ISO 8601 format, in UTC. |
|
The message. Error and fatal messages start with |
|
Optional. Details of the entry, such as the error |
|
Optional. The stack trace of an error. |
For example, a PostgreSQL deployment without the required schema stops with this entry:
{"level":60,"time":"2026-05-05T11:19:07.634Z","msg":"[FATAL] PostgreSQL Error: schema \"cs-on-premises\" does not exist","data":{"name":"PostgreSQL Error","message":"PostgreSQL Error: schema \"cs-on-premises\" does not exist"}}
To show only error and fatal entries, filter the log with jq. The fromjson? filter skips any line that is not JSON:
docker logs ai-service 2>&1 | jq -R -c 'fromjson? | select(.level >= 50)'
Log levels
The LOG_LEVEL environment variable sets the lowest level that the AI service writes. The default is 40, so the AI service does not write trace, debug, and info entries. Keep LOG_LEVEL at 40 or higher in production: lower levels write a large volume of entries, which can affect performance and memory use.
| Level | Name | Meaning |
|---|---|---|
|
Trace |
The most detailed information about the processes of the AI service. |
|
Debug |
Diagnostic detail for cases that are hard to debug. |
|
Info |
A change to a resource, an audit entry, or a request summary line. |
|
Warn |
A recoverable problem: a step that failed and then succeeded on retry, a side effect of asynchronous event processing, or a minor problem with the behavior of a model. |
|
Error |
A request or a process that failed. |
|
Fatal |
An error that stops the AI service. |
A small, steady number of warnings about model behavior is normal. These warnings typically report a tool that the model called with invalid input. The AI service returns the error to the model, and the model corrects the call.
With ENABLE_METRIC_LOGS=true, the AI service writes request summary lines at level 30, whatever the value of LOG_LEVEL. See Structured metric logs.
Notable log messages
Start-up failures are logged with the prefix [FATAL] and stop the AI service. For those messages and their fixes, see Container startup failures. For MCP and web search messages, see MCP troubleshooting.
| Message | Meaning | Action |
|---|---|---|
|
The AI service started and accepts requests. |
No action. |
|
The message request failed with HTTP |
Add credentials that the service can use for the provider. See Large language model (LLM) provider errors. |
|
The file upload failed with HTTP |
Turn off uniform bucket-level access on the bucket. See GCP (GKE). |
Tracing a failed request
Every error response from the AI service carries a traceId that identifies the request. For the error response shape, see Error responses.
The TinyMCE AI plugin prints each error in the browser console as a TinyMCE AI Error: entry, followed by the status code and, when the response carries them, a Trace ID: line and the error code.
Diagnostic recipes
Expand for copy-ready diagnostic commands
Tail logs:
docker logs ai-service --tail 200 -f
Liveness check:
curl -fsS http://localhost:8000/health
Decode a JWT (inspect payload without verifying):
python3 -c "import jwt,sys,json; print(json.dumps(jwt.decode(sys.argv[1], options={'verify_signature': False}), indent=2))" <token>
Recreate after an env change:
docker compose up -d --force-recreate ai-service
Inspect effective environment:
docker inspect ai-service | jq '.[0].Config.Env'
Validate PROVIDERS JSON:
echo "$PROVIDERS" | jq .
Test data layer connectivity from inside the container:
docker exec ai-service /bin/sh -c "nc -zv mysql 3306"
docker exec ai-service /bin/sh -c "nc -zv redis 6379"
Replace mysql and redis with the actual hostnames configured in DATABASE_HOST and REDIS_HOST. If the AI service was started with docker compose rather than standalone docker run, use docker compose exec ai-service instead.
End-to-end smoke test (token mint through streamed response):
TOKEN=$(curl -s -X POST http://localhost:3000/api/ai-token | jq -r '.token')
curl -s -X POST http://localhost:8000/v1/conversations \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"smoke-1","title":"Smoke test"}'
# Replace 'agent-1' with a model ID from MODELS if explicitly configured
curl -N -X POST http://localhost:8000/v1/conversations/smoke-1/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prompt":"Say hi in five words.","model":"agent-1"}'