Runbook: Scanner — Worker Not Processing Jobs
Audience: Platform on-call responding to stalled or backlogged Stella Ops scan jobs.
Purpose: Diagnose and clear a Scanner worker that has stopped processing the scan queue — most commonly an out-of-memory kill, a lost Valkey/PostgreSQL connection, or a deadlocked worker pool — and restore end-to-end scan processing.
Metadata
| Field | Value |
|---|---|
| Component | Scanner |
| Severity | Critical |
| On-call scope | Platform team |
| Last updated | 2026-05-31 |
| Doctor checks | check.scanner.queue, check.scanner.resources |
| Related runbooks | scanner-oom · scanner-timeout · scanner-sbom-generation-failed · scanner-registry-auth |
Reconciliation note (2026-05-31): This runbook was reconciled against
src/(Scanner, the Doctor scanner/storage plugins, and the CLI command factory). Several commands and signal names in the original draft did not exist in the codebase and have been corrected or flagged. See the inline NOT IMPLEMENTED callouts where a referenced capability has no backing code yet. The 2026-05-31 pass additionally: corrected the worker-concurrency config key (Scanner:Worker:MaxConcurrentJobs, notScanner__Worker__Concurrency), added the required--from/--toarguments tostella obs logs, and flagged that the Doctor checks’ own remediation steps print non-existent CLI commands.
Symptoms
- [ ] Scan jobs stuck in “pending” or “processing” state for >5 minutes
- [ ] Scanner worker process shows 0% CPU usage
- [ ]
stella doctor --check check.scanner.queuereports stuck jobs or a growing backlog - [ ] UI shows “Scan in progress” indefinitely
- [ ] Scanner queue counters (
scanner_queue_enqueued_totalrising whilescanner_queue_ack_totalis flat) indicate jobs are not being completed
NOT IMPLEMENTED — alerts. The original draft referenced alerts
ScannerWorkerStuckandScannerQueueBacklog. Neither exists indevops/telemetry/alerts/. The only Scanner alert rules shipped today are the FN-drift SLO alerts indevops/telemetry/alerts/scanner-fn-drift-alerts.yaml(ScannerFnDriftWarning,ScannerFnDriftCritical,ScannerFnDriftEngineViolation), which are unrelated to worker/queue stalls. A worker-stall / queue-backlog alert is a prevention gap (see Prevention).
NOT IMPLEMENTED — metric. The draft metric
scanner_jobs_pendingdoes not exist. The Scanner queue meter (StellaOps.Scanner.Queue→src/Scanner/__Libraries/StellaOps.Scanner.Queue/QueueMetrics.cs) emits only:scanner_queue_enqueued_total,scanner_queue_deduplicated_total,scanner_queue_ack_total,scanner_queue_retry_total, andscanner_queue_deadletter_total(each tagged bytransport). Derive a “pending” signal fromenqueued_total - ack_totalrather than referencing a non-existent gauge.
Impact
| Impact Type | Description |
|---|---|
| User-facing | New scans cannot complete, blocking CI/CD pipelines and release gates |
| Data integrity | No data loss; pending jobs will resume when the worker recovers |
| SLA impact | Scan latency SLO violated if not resolved within 15 minutes |
Diagnosis
Quick checks (< 2 minutes)
Check Doctor diagnostics for the scanner queue:
stella doctor --check check.scanner.queueThis is the
ScannerQueueHealthCheck(src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Scanner/Checks/ScannerQueueHealthCheck.cs). It fails whenstuckJobs > 0, when queue depth ≥ 500, or when the failure rate ≥ 15%, and warns at queue depth ≥ 100 / failure rate ≥ 5% / a growing backlog.NOT IMPLEMENTED — backing endpoint. This check calls the Scanner web service at
GET /api/v1/queue/stats, but no such endpoint is registered insrc/Scanner/StellaOps.Scanner.WebService/Endpoints/. Until that endpoint ships, the check degrades to Warn (“Cannot retrieve queue stats: HTTP 404”) rather than reporting real stuck-job counts. Treat a Warn here as “telemetry unavailable”, not “healthy”, and fall back to the manual checks below. (The check is also gated byCanRun: it only runs whenScanner:Url/Services:Scanner:Urlis configured, otherwise it is skipped entirely.)NOT IMPLEMENTED — Doctor remediation commands. When this check does fire, its built-in remediation steps print commands that do not exist in the CLI:
stella scanner queue list --status stuck,stella scanner queue retry --stuck,stella scanner workers status, andstella scanner workers scale --replicas 4(seeScannerQueueHealthCheck.cs/ScannerResourceUtilizationCheck.cs). Ignore those suggestions and follow the Resolution section below, which uses only implemented verbs and the container runtime. (Fixing the Doctor plugin’s remediation text is a separate source-side task, out of scope for this runbook.)Check scanner resource / worker-pool health:
stella doctor --check check.scanner.resourcesThis is the
ScannerResourceUtilizationCheck(src/Doctor/.../Checks/ScannerResourceUtilizationCheck.cs). It reports CPU, memory, and worker-pool occupancy (active / idle / total workers) and warns when all workers are busy with zero idle capacity.NOT IMPLEMENTED — backing endpoint. Same caveat: this check calls
GET /api/v1/resources/stats, which is not registered in the Scanner web service, so it currently degrades to Warn.Inspect the scanner container directly (authoritative fallback while the stats endpoints are unimplemented):
docker ps --filter name=scanner docker stats --no-stream <scanner-container> docker logs --tail 100 <scanner-container>Look for: 0% CPU on the worker,
OOMKilled, repeated restarts, orconnection refused/timeouterrors against Valkey or PostgreSQL.
Deep diagnosis
Check worker process logs via the observability log surface or the container directly:
stella obs logs --service scanner --from 2026-05-31T00:00:00Z --to 2026-05-31T01:00:00Z(
stella obs logsis the implemented log-query verb; there is nostella scanner logscommand.--fromand--toare required ISO-8601 timestamps — the command will not parse without them — and you can add--level error --query timeoutto narrow the window. SeeBuildObsCommandinsrc/Cli/StellaOps.Cli/Commands/CommandFactory.cs.) Or, from the host:docker logs --tail 200 <scanner-container> 2>&1 | grep -Ei "timeout|refused|out of memory|oom"Check Valkey connectivity (job queue transport).
NOT IMPLEMENTED — Doctor check. The draft referenced
stella doctor --check check.storage.valkey. No Valkey/Redis Doctor check exists. The Storage plugin (src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Storage/) ships onlycheck.storage.diskspace,check.storage.evidencelocker, andcheck.storage.backup. Verify Valkey manually instead:docker exec <valkey-container> redis-cli ping # expect: PONGCheck if the worker container was OOM-killed:
docker inspect <scanner-container> --format '{{.State.OOMKilled}} {{.State.ExitCode}}'Look for:
true(OOMKilled), exit code137(SIGKILL / OOM) or143(SIGTERM).Check resource utilisation via the implemented observability command:
stella obs top --service scanner(
stella obs topstreams per-service health, SLO status, and queue details. There is nostella obs metricssubcommand.) Look for: memory > 90%, CPU sustained > 95%.
Resolution
Immediate mitigation
NOT IMPLEMENTED — worker lifecycle verbs. The original draft used
stella scanner workers restart,stella scanner workers inspect,stella scanner workers scale,stella scanner scale, andstella service restart scanner. None of these exist. The implementedstella scannersurface is:download,workers get,workers set,secrets …, andplugins …(seesrc/Cli/StellaOps.Cli/Commands/CommandFactory.cs→BuildScannerCommand). Worker restart/scaling is performed through the container runtime today.
Restart the scanner service (terminates and respawns workers):
docker compose -f devops/compose/docker-compose.stella-ops.yml restart scannerVerify workers are processing by re-running the queue check and watching the queue counters trend (ack rate should rise relative to enqueue rate):
stella doctor --check check.scanner.queue stella obs top --service scanner --refresh 5
Root cause fix
If workers were OOM-killed:
Raise the scanner container memory limit in the compose / deployment manifest (e.g.
mem_limit/deploy.resources.limits.memory) and redeploy. There is nostella scanner config set worker.memory_limitcommand.Reduce concurrent scans by lowering the worker count:
stella scanner workers set --count 2then restart the scanner container so the new configuration is picked up. (
stella scanner workers setwrites the worker count/pool config —--countis required,--pooldefaults todefault. There is nostella scanner config set worker.concurrencycommand.)CONFIG GAP — verify wiring.
stella scanner workers setpersists a local CLI config file (STELLAOPS_CLI_WORKERS_CONFIG). The CLI only feeds that value to the worker as a--workersargument onstella scan run(seeCommandFactory.cs, whereLoadScannerWorkerConfig().IsConfiguredappends--workers <count>); a long-running scanner service started outsidescan rundoes not read that file. To cap concurrency on the service itself, set the worker’sMaxConcurrentJobs(config sectionScanner:Worker, env-var formScanner__Worker__MaxConcurrentJobs; default2) — seesrc/Scanner/StellaOps.Scanner.Worker/Options/ScannerWorkerOptions.cs. The env varScanner__Worker__Concurrencyreferenced in earlier drafts does not exist;MaxConcurrentJobsis the real key.
If the Valkey connection failed:
Verify Valkey health manually (no Doctor check exists — see Deep diagnosis):
docker exec <valkey-container> redis-cli pingRestart Valkey if needed, then restart the scanner so it re-establishes the queue transport.
NOT IMPLEMENTED — referenced runbook. The draft pointed to
valkey-connection-failure.md, which does not exist in this runbook set. Cross-reference removed; create that runbook before re-linking it.
If workers appear deadlocked:
Capture diagnostics, then restart the scanner container to clear the stall:
docker logs --tail 500 <scanner-container> docker compose -f devops/compose/docker-compose.stella-ops.yml restart scannerNOT IMPLEMENTED — deadlock toggle. The draft
stella scanner config set worker.deadlock_detection truehas no backing command or config key insrc/. Removed pending implementation.
Verification
# Verify the queue and resource checks (telemetry-dependent — see endpoint caveats above)
stella doctor --check check.scanner.queue
stella doctor --check check.scanner.resources
# Submit a real scan to confirm end-to-end processing
stella scan run --runner docker --entry <scanner-image> --target <path>
# Watch queue counters / worker health drain the backlog
stella obs top --service scanner --refresh 5
# Verify no errors in recent logs (--from/--to are required ISO-8601 timestamps)
stella obs logs --service scanner --level error --from 2026-05-31T00:00:00Z --to 2026-05-31T01:00:00Z
NOT IMPLEMENTED — test-scan shortcut. The draft
stella scan image --image alpine:latest --dry-rundoes not match the CLI. The implementedstella scan imagegroup only exposesinspectandlayers(and those currently print placeholder output). Usestella scan run …for a real scan, orstella scan image inspect --ref <ref>for metadata only.
Prevention
- [ ] Alert (GAP): No
ScannerWorkerStuck/ScannerQueueBacklogalert exists. Add a queue-stall alert (e.g. onscanner_queue_enqueued_totalrising whilescanner_queue_ack_totalis flat) underdevops/telemetry/alerts/. - [ ] Stats endpoints (GAP): Implement
GET /api/v1/queue/statsandGET /api/v1/resources/statsin the Scanner web service socheck.scanner.queue/check.scanner.resourcescan detect real stalls instead of degrading to Warn. - [ ] Monitoring: Add a Grafana panel for scanner worker memory usage and queue ack/enqueue rates.
- [ ] Capacity: Review worker count (
stella scanner workers get) and container memory limits during capacity planning.
Related Resources
- Architecture:
docs/modules/scanner/architecture.md - Related runbooks (present in this set):
scanner-oom.md,scanner-timeout.md,scanner-sbom-generation-failed.md,scanner-registry-auth.md - Doctor checks:
src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Scanner/Checks/ScannerQueueHealthCheck.cssrc/Doctor/__Plugins/StellaOps.Doctor.Plugin.Scanner/Checks/ScannerResourceUtilizationCheck.cs
- Scanner CLI surface:
src/Cli/StellaOps.Cli/Commands/CommandFactory.cs(BuildScannerCommand) - Queue metrics:
src/Scanner/__Libraries/StellaOps.Scanner.Queue/QueueMetrics.cs - Dashboard: Grafana > Stella Ops > Scanner Overview
