INCIDENT: production has delivered no alerts since 2026-08-11 — crash-looping on a stale image against a forward-migrated DB #186
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#186
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: high. Production severe-weather alerting has been silently down for ~9.5 days. Found incidentally on 2026-08-20 while querying production for an unrelated
radar_animateddistribution (#184).Impact
2026-08-11 03:27:05 UTC. Nothing since.sent_alertsholds 1,719 rows and has not grown in 9.5 days.postgres,signal-cli) areUp 9 days (healthy), so a container-level health view looks partly fine.Root cause
The database is at Alembic revision
0033(shipped in v1.8.1). The container is runningweatherbot-app— a locally-built image reportingWEATHERBOT_VERSION=v1.7.0-29-g12d47dd, which predates0033and does not contain it. Startup therefore dies every time:app/main.py's lifespan runsalembic upgrade headwithcheck=True, so a migration failure is fatal to startup by design — correct behaviour, but it means a code/DB mismatch is an unrecoverable crash loop rather than a degraded start.How the mismatch happened
The running container's compose labels point at the old pre-Ansible checkout:
while the Ansible-managed deployment lives at
/opt/weatherbot-svc. Both use the compose project nameweatherbot, so they own the same containers. Something randocker compose upfrom/home/ryan/WeatherBoton ~2026-08-11 05:54 (containerCreatedtimestamp), which rebuilt the app from that stale checkout and replaced the Ansible-deployed v1.8.1 container — while leaving the database forward-migrated at0033.This is exactly the failure mode
.forgejo/workflows/cd.ymldocuments as the reason the merge-time dev deploy was retired: "it rebuilt the stack from a git checkout under the same compose project name the Ansible role owns." The same landmine existed on production and was never disarmed.weatherbot.service(the Ansible-managed systemd unit) is present but infailedstate.Proposed remediation
docker-host. Thegroup_varspin is1.8.1, whose schema is exactly0033, so it matches the database with no migration needed. This is the committed intended state./home/ryan/WeatherBotmust stop being able to clobber the Ansible stack. Either remove it, or rename its compose project so it can never again ownweatherbot's containers. Restoring service without this leaves the same trap armed.Not the cause
The
weatherbot-backup.timerisactiveand its serviceinactive dead(normal between runs). Postgres and signal-cli are healthy. This is purely the app container.Incidental finding (the query that surfaced this)
notification_channelson production: 4 rows, allradar_animated = true. So #184's migration0037backfill is a no-op on production as well as dev — no channel has opted out of radar, so none can be wrongly switched on. The migration remains correct; there is simply no row that exercises it.Resolved — service restored 2026-08-20 ~21:47 UTC
Production is running
git.rhoving.com/rbrooks/weatherbot:1.8.1(the registry image the pin specifies, not a local build), healthy, with the full Ansible-managed stack recreated (postgres:16.14-alpine, signal-cli).Confirmed working end-to-end, not merely container-healthy: all APScheduler jobs execute successfully (
poll_alerts_job,poll_spc_job,process_delivery_outbox_job, …), andsent_alertsrecorded a delivery at2026-08-20 21:51:25 UTC— the first since 2026-08-11 03:27. Total outage: ~9.7 days. Database remains at0033, matching 1.8.1, so no migration was needed.Correcting the root cause above
My original attribution — "something ran
docker compose upfrom/home/ryan/WeatherBot" — was stated with more confidence than the evidence supported. The journal now shows a more specific first domino:weatherbot.servicetimed out duringdocker compose upbecause Postgres took 66s to become healthy, and systemd SIGINT'd compose with the stack half-started — the app container never came up under the unit.This is precisely the failure the systemd-unit change in iac-repo#277 was written to fix, whose own comment reads: "is slow to become healthy,
compose upstops waiting, and the unit dies with the stack half-started." Production had not received that change.What happened between
05:40:59(unit failed) and05:54:48(the crash-loopingweatherbot-appcontainer's creation timestamp) is not established by evidence I have. The 14-minute gap and the container's build-from-source origin at/home/ryan/WeatherBotare consistent with a manual recovery attempt from the old pre-Ansible checkout, but I did not find a log proving that, and I should not have asserted it as fact. What is certain is that the resulting container was built from stale v1.7.0-era source and could not satisfy the database's0033, producing 13,294 restarts.So the chain was: iac-repo#277 missing on prod → compose-up timeout on slow Postgres → half-started stack → recovery from a stale checkout → permanent version/schema mismatch.
Still outstanding
weatherbot.serviceremains infailedstate (stale, from Aug 11) while the containers run healthy outside it. The unit file is now correct —WorkingDirectory=/opt/weatherbot-svc,TimeoutStartSec=300, and #277'sRestart=on-failure/RestartSec=30are all present — so this is leftover state, not a live fault. It wantssystemctl reset-failed weatherbot.servicefollowed bysystemctl start weatherbot.serviceso the unit adopts the running stack. Until then, anything monitoring unit state reports a failure that no longer exists./home/ryan/WeatherBot/docker-compose.ymlstill exists ondocker-host, andweatherbot-app:latestis still in the local image store. Both should go, or the checkout's compose project renamed, so it can never again own theweatherbotcontainers.