Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/incidents/2026-07-07-hull-live-clinic-gateway-failure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Incident review: Hull live clinic gateway failure, 6–7 July 2026

**Severity**: No patient impact. The clinic ran to completion on the manual-images fallback. Gateway (automatic) imaging was unavailable for the first live clinic; full end-to-end success was achieved the same afternoon.

## Summary
The first production use of the gateway imaging flow failed: worklist items sent from Rubie never reached the hospital gateway. Diagnosis was unusually hard because three independent faults were stacked, all producing the same symptom (a 30-second timeout on every send), and the system’s error reporting could not distinguish them. Each fault was peeled off in turn over ~36 hours; the full pipeline – check-in → worklist → modality → images → upload → Rubie – worked in production for the first time on the afternoon of 7 July.

## Symptoms
- Every worklist send from prod Rubie failed with a generic “Timeout waiting for response from gateway”; nothing appeared on the modality worklist.
- All infrastructure looked healthy: relay listener connected (count 1), managed identity and role assignments correct, gateway services running, config verified repeatedly on both sides.
- Independent probes succeeded where the app failed (a laptop-based relay echo round-tripped in 0.4s), deepening the confusion.

## Root causes
- **Private DNS blackhole**: the relay hostname resolved, inside the prod VNet, to the relay namespace’s private endpoint, which was silently broken despite showing *Approved* (later attributed to an Azure-side fault or provisioning race at PE creation; resolved weeks later by deleting and recreating the PE, which has worked since).
- **Runtime incompatibility**: the asyncio websockets client cannot reliably open connections inside gevent-patched gunicorn workers (DNS resolution via asyncio's thread executor degrades to 10s+, exhausting the connection budget). Every environment where sends had ever succeeded bypassed gevent (local dev, shell probes) – the production execution model had never actually been tested.
- **Silent listener death**: overnight, the gateway's relay connection died without the process noticing or the relay deregistering it – a “zombie” listener that looked connected while receiving nothing.

**Contributing factors**: relay send failures were recorded only in a database field, not logged; a single exception handler conflated “connection never opened” with “gateway never replied”; VM telemetry export was broken throughout, forcing diagnosis via remote commands; there was no admin visibility of gateway actions at the start of the incident.

## What we did
- Shipped diagnostic capability during the incident: a GatewayActions admin page.
- Built a standalone relay echo probe to test the relay → gateway path with Rubie removed; its container-side variant exposed the DNS/PE fault.
- Fixed DNS (interim public CNAME; later superseded by PE recreation), replaced the asyncio client with the synchronous websockets client ([Rubie ADR-009](https://github.com/NHSDigital/dtos-manage-breast-screening/blob/main/docs/adr/ADR-009-Synchronous_websockets_client_for_relay_sends.md)), and adopted a pre-clinic listener restart to clear zombie connections.
- Invoked the designed fallback: the clinic ran entirely on manual image recording, invisibly to participants.
- Relay token refresh implemented

## Improvements made since
- **Error reporting**: every relay failure now names its failing phase in both the admin and logs; unknown actions get structured error replies instead of silent connection closes.
- **Runbooks**: [clinic preparation](../deployment/runbooks/clinic-preparation.md) checklist; [live-clinic debugging](../deployment/runbooks/live-clinic-debugging.md); onboarding/promotion updates; diagnostic toolkit ships with the gateway release.
- **Automated end-to-end test participant**: a synthetic test can now be sent through the real production pipeline on demand – Rubie dispatches a test worklist item over the relay; the gateway stores it and invokes its modality emulator in place of the physical machine, which queries the worklist, generates DICOM images, and submits them through the standard PACS-and-upload path back to Rubie, where the round trip is confirmed against a dedicated test-action record (kept separate from clinical data).
- **Rehearsal practice**: successful remote dummy run completed ahead of the next live clinic.
- **In flight**: automated per-gateway healthcheck (health proven by traffic, surfaced in admin, wired into deploy pipelines); per-site imaging toggle so switching a site to manual is an audited admin action rather than an emergency deploy or record deletion; gateway listener liveness/token-renewal fixes.

## Learnings
- Health must be proven by traffic, not state. Listener counts, PE connection status, and service states each individually misled us during this incident. Everything we’ve built since sends a real message and judges the reply.
- The fallback earned its keep. Manual-images mode meant zero patient impact and removed all time pressure from diagnosis.
- Institutional knowledge belongs in the repo. Every finding was folded into version-controlled runbooks the same week, several of which were corrected again as evidence improved.
- We had no production gateway we could actually touch. Every diagnostic step on the gateway side ran through Arc run-commands against a hospital-owned machine – serially queued, minutes per round trip, dependent on hospital IT for RDP and power, and twice found switched off entirely. A team-operated gateway VM in the production environment (Azure-hosted, Arc-onboarded, ring0, connected to the prod relay and API but to no modality) would have let us reproduce and inspect the failures directly under real prod identity and networking.
Loading