| name | debugging-operations |
| description | Diagnose runtime and integration issues across the API and Worker services. Use when services fail to boot, workers do not connect, LLM jobs stall, SSE streams are silent, database operations fail, or Docker networking breaks. |
Debugging and Operations
Container Architecture
| Service | Container | Port (host) | Port (container) | Compose file | Network |
|---|
| API | api-api-1 | 80 (HTTP), 3030 (WebSocket) | 3000 | api/compose.yaml | llm |
| Worker | worker-worker-1 | none | 3000 | worker/compose.dev.yaml | llm |
| MySQL | mysql-mysql-1 | 3306 | 3306 | api/compose.yaml | llm |
All services communicate over the Docker bridge network llm.
Quick Start & Health Checks
Check Container Status
docker compose -f api/compose.yaml ps
docker compose -f worker/compose.dev.yaml ps
Expected: all services in running state.
Health Probes
curl http://localhost/ready
docker compose -f worker/compose.dev.yaml exec worker curl http://localhost:3000/ready
Tail Logs
docker compose -f api/compose.yaml logs --tail=100 -f api
docker compose -f worker/compose.dev.yaml logs --tail=100 -f worker
docker compose -f api/compose.yaml logs --tail=50 mysql
Shell into Containers
docker compose -f api/compose.yaml exec api sh
docker compose -f worker/compose.dev.yaml exec worker sh
docker compose -f api/compose.yaml exec mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DATABASE}
Troubleshooting Matrix
Issue: API Container Fails to Start
Symptom: docker compose -f api/compose.yaml up exits immediately or throws error.
Steps:
-
Check logs for error message:
docker compose -f api/compose.yaml logs api
-
Common causes:
-
Port 80 already in use — another service running on port 80
lsof -i :80
Solution: Either kill the process or change ports: [ "80:3000" ] in api/compose.yaml
-
Environment variables missing — check .env in api/ directory
ls -la api/.env
cat api/.env
-
MySQL connection fails — MySQL container not running or network llm doesn't exist
docker compose -f api/compose.yaml ps mysql
docker network ls | grep llm
-
Restart the service:
docker compose -f api/compose.yaml restart api
Issue: Worker Cannot Connect to API
Symptom: Worker logs show repeating errors like:
[timestamp] API WebSocket error: ECONNREFUSED or Connection rejected
[timestamp] Retrying WebSocket connection in Xms...
Steps:
-
Verify Worker has correct API_WS_URL:
docker compose -f worker/compose.dev.yaml exec worker env | grep API_WS_URL
If API_WS_URL is pointing to localhost or external IP, update worker/.env:
echo "API_WS_URL=ws://api:3000/ws/workers" >> worker/.env
-
Verify both containers are on the llm network:
docker network inspect llm
-
Verify API WebSocket server is running and listening:
docker compose -f api/compose.yaml exec api curl http://localhost:3000/ready
-
Test WebSocket connectivity manually from worker container:
docker compose -f worker/compose.dev.yaml exec worker sh
apk add --no-cache curl websocat
websocat ws://api:3000/ws/workers
-
Restart worker service:
docker compose -f worker/compose.dev.yaml restart worker
Issue: Worker Registered but Jobs Not Dispatching
Symptom:
GET /ready shows connectedWorkers > 0
POST /tasks/run or POST /v1/chat/completions returns SSE stream but no output
- API logs don't show dispatch message
Steps:
-
Check if worker is actually ready (not busy):
curl http://localhost/ready
-
Check API logs for job enqueue:
docker compose -f api/compose.yaml logs --tail=200 api | grep -i "dispatch\|enqueue\|job"
-
Check worker logs for job receipt:
docker compose -f worker/compose.dev.yaml logs --tail=200 worker | grep -i "stream-job\|dispatch"
-
If no "dispatch" message in API logs:
- StreamRouter may not have enqueued the job
- Check validation errors in API logs
- Verify
model in request matches a registered worker's model
-
If worker received job but produced no output:
- Check LLM model runner is reachable
- Check
MODEL_RUNNER_HOST env var in worker
- Test LLM runner directly (see below)
Issue: LLM Model Runner Unreachable
Symptom: Worker logs show:
Error calling LLM API: ECONNREFUSED or Connection timeout
Steps:
-
Verify MODEL_RUNNER_HOST in worker environment:
docker compose -f worker/compose.dev.yaml exec worker env | grep MODEL_RUNNER_HOST
-
Test connectivity from worker container:
docker compose -f worker/compose.dev.yaml exec worker sh
curl -v http://${MODEL_RUNNER_HOST}/engines/llama.cpp/v1/chat/completions
-
If unreachable:
- Is the LLM runner service running and exposed?
- Is
MODEL_RUNNER_HOST correct (hostname vs IP)?
- If LLM runner is outside Docker, is it accessible from
llm network?
- Update
worker/.env if needed and restart
Issue: MySQL Cannot Connect or Database Not Initialized
Symptom: API logs show:
Error: connect ECONNREFUSED 127.0.0.1:3306
or
Error: ER_NO_DB_ERROR
Steps:
-
Verify MySQL container is running:
docker compose -f api/compose.yaml ps mysql
-
Check MySQL logs for initialization errors:
docker compose -f api/compose.yaml logs mysql
-
Test MySQL connectivity:
docker compose -f api/compose.yaml exec api \
sh -c 'mysql -h mysql -u root -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1;"'
-
Verify schema was created:
docker compose -f api/compose.yaml exec api \
sh -c 'mysql -h mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DATABASE} \
-e "SHOW TABLES;"'
-
If schema not created:
-
Restart MySQL to force re-initialization:
docker compose -f api/compose.yaml down mysql
docker compose -f api/compose.yaml up -d mysql
Advanced Debugging
WebSocket Message Inspection
Intercept WebSocket messages between API and worker:
docker compose -f api/compose.yaml exec api sh
netstat -an | grep 3030
tcpdump -i eth0 'port 3030' -A
Alternatively, add console.log in api/helpers/wsserver.js:
this.ws.on('message', (message) => {
const parsed = JSON.parse(message);
console.log(`[WebSocket message] type=${parsed.type}`, JSON.stringify(parsed.payload).slice(0, 100));
});
Database Query Logging
To see SQL queries executed, add logging to api/helpers/mysql.js:
async find(table, { filter = {}, view = [], opt = {} } = {}) {
const query = this.#buildFindQuery(table, filter, view, opt);
console.log(`[MySQL query]`, query);
const results = await this.connection.query(query);
return results;
}
Then restart API and tail logs:
docker compose -f api/compose.yaml restart api
docker compose -f api/compose.yaml logs --tail=100 -f api | grep "\[MySQL query\]"
Worker Reconnect Backoff Timing
Worker reconnects with exponential backoff (1s → 10s). Monitor in logs:
docker compose -f worker/compose.dev.yaml logs --tail=50 -f worker | grep -i "reconnect\|retrying"
Performance/Memory Issues
Check resource usage:
docker stats --no-stream
curl http://localhost/ready | jq .queuedJobs
docker compose -f worker/compose.dev.yaml logs --tail=50 worker
Rebuild and Clean Start
When making code changes or suspect stale Docker state:
docker compose -f api/compose.yaml down
docker compose -f worker/compose.dev.yaml down
docker compose -f api/compose.yaml up -d --build
docker compose -f worker/compose.dev.yaml up -d --build
sleep 10
curl http://localhost/ready
docker compose -f worker/compose.dev.yaml logs worker | tail -20
Logging Strategy (Current)
- Framework:
console.log (no structured logging library)
- Output: STDOUT/STDERR captured by Docker
- Limitations:
- No log levels (debug/info/warn/error)
- No timestamps in most logs (container adds them)
- No request ID correlation across services
- No sampling for high-volume logs
Improvement roadmap:
- Consider
pino or winston for structured JSON logs
- Add request IDs for tracing across services
- Add debug-level verbosity flag
Checklist for "Service Not Working"
Use this flow to diagnose most issues:
- ✅ Are all containers running? (
docker compose ps)
- ✅ Is API responding to
/ready? (curl http://localhost/ready)
- ✅ Are workers connected? (
connectedWorkers > 0 in /ready response)
- ✅ Are workers available? (
availableWorkers > 0)
- ✅ Can you reach worker health endpoint? (
docker exec worker curl ...)
- ✅ Does API show job dispatch in logs? (
grep dispatch api logs)
- ✅ Does worker show job receipt in logs? (
grep stream-job worker logs)
- ✅ Is LLM runner reachable? (
curl ${MODEL_RUNNER_HOST}/...)
- ✅ Is MySQL running and schema loaded? (
mysql ... SHOW TABLES;)
- ✅ Check environment variables in
.env files match Docker setup
curl -N -X POST http://localhost/stream \
-H "Content-Type: application/json" \
-d '{"message":"Say hello","model":"ai/smollm2"}'
Expected output: a sequence of SSE lines:
event: message
data: Hello
event: end
data: Stream complete.
Validation error (400)
curl -X POST http://localhost/stream \
-H "Content-Type: application/json" \
-d '{"message":"Hello"}'
curl -X POST http://localhost/stream \
-H "Content-Type: application/json" \
-d '{"model":"ai/smollm2"}'
SSE stream is silent after connection
- Check
connectedWorkers and availableWorkers in /ready. If both are 0, the worker is not connected.
- Check
queuedJobs — if it keeps growing, workers are connected but not processing (check worker logs).
- Check worker logs for
Error calling LLM API: which means the model runner is unreachable.
LLM Model Runner Debugging
The worker calls:
POST {MODEL_RUNNER_HOST}/engines/llama.cpp/v1/chat/completions
docker compose -f worker/compose.dev.yaml exec worker \
sh -c 'curl -s $MODEL_RUNNER_HOST/engines/llama.cpp/v1/chat/completions \
-H "Content-Type: application/json" \
-d "{\"model\":\"$MODEL_RUNNER_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"stream\":true}"'
Common failures:
MODEL_RUNNER_HOST not set or wrong — worker logs show Error calling LLM API: Failed to fetch.
- Model runner not running — HTTP connection refused.
- Wrong model name — model runner returns
4xx; worker logs show Model call failed with status <code>.
Queue and Worker State
Use GET /ready to inspect the current state at any time:
| Field | Meaning |
|---|
connectedWorkers | Workers with open WebSocket connections to API |
availableWorkers | Workers currently idle and ready to accept jobs |
activeJobs | Jobs currently being processed by a worker |
queuedJobs | Jobs waiting for an available worker |
If queuedJobs grows unbounded: workers are either all busy or disconnected.
If availableWorkers is 0 but connectedWorkers > 0: all workers are busy; this is normal under load.
Common Failure Points
| Symptom | Likely cause | Check |
|---|
connectedWorkers: 0 | Worker not running or wrong API_WS_URL | Worker logs; env var |
| SSE request hangs forever | No available worker; job queued | GET /ready — queuedJobs > 0 |
SSE sends event: error | LLM model runner returned error | Worker logs for Error calling LLM API: |
| Worker reconnect loop | API WebSocket not reachable | API running? Network? API_WS_URL? |
400 on POST /stream | Missing or invalid message or model | Check request body |
llm network not found | API compose not started | docker compose -f api/compose.yaml up first |
Useful Files During Debugging
api/app.js — route registration, WSServer setup
api/helpers/router.js — StreamRouter dispatch, worker lifecycle
api/helpers/wsserver.js — WebSocket event routing
api/helpers/stream.js — SSE response wrapper
api/helpers/queue.js — FIFO job queue
worker/app.js — worker entry, ApiStreamClient initialization
worker/helpers/api-client.js — WebSocket connection, reconnect logic, job handling
worker/model/llm.js — LLM model runner fetch + stream processing
api/compose.yaml — API compose config
worker/compose.dev.yaml — Worker compose config