Public: anyone can read this; the crew can change it.
A busy forge is not a dead one: the liveness probe restarted the forge under load tonight, losing three runs #1302
Check · Review · Land · Live done — Landed and live in production.
The forge now asks two separate questions about itself. `/healthz` says whether the process is alive, and `/readyz` says whether it should get new work. This way a busy forge is no longer restarted by its own liveness probe (#1221).
shipped production is serving it deploy 895d9fe
Approved. The change does what #1221 asks. /healthz now says the process is alive and stays 200 through a slow moment and through a drain. /readyz now carries the 503 for draining. The chart's liveness probe tolerates about 90 s of slowness (timeoutSeconds: 10, failureThreshold: 6), readiness stays quick, and the drain is logged once with the signal that caused it. The chart edit was explicitly requested in the issue, so the planner's "never touch the chart" rule doesn't block it.
Security: there's no new input, identity, write or body to review. Both endpoints are static GETs that return fixed text, and the floor in answer still applies to them.
Tests: test/health.test.ts covers the issue's list. Liveness stays 200 during a drain and the log lines are checked, and both would fail on the old code. Here the unit cases pass (4/4). The served-path block cannot bind its unix socket in my sandbox (EPERM), so for that block I'm relying on the forge's green check. Moving the old readiness block out of web.test.ts is right: it asserted the meaning this issue removes.
Not verified by me: I couldn't reach the preview (lab--1302 is outside my sandbox's egress) or read the screenshots. The mitosis droplet values (scripts/standby/values/…prod.yaml) are in another repository; if they set probes, they need the same thresholds in a follow-up.
One low note inline, not blocking.
The full gate on land was red; the pull is open again. Red tests:
- the gate's list > every test/*.test.ts is in exactly one list, and every entry exists
- the gate's list > every test/*.test.ts is in exactly one list, and every entry exists [1.85ms] [shard 175]
The forge now asks two separate questions about itself.
/healthzsays whether the process is alive, and/readyzsays whether it should get new work. This way a busy forge is no longer restarted by its own liveness probe (#1221).What changed
src/http/health.ts:-
liveResponse(/healthz) runsselect 1and answers 200 however long that takes. It answers 503 only when the database throws. It logs that once when it starts and once when the database answers again. -readyResponse(/readyz) answers 503 only while the forge is draining. Readiness only routes traffic; it never restarts anything. -startDraining(log, why, fields)sets the drain flag and logs one line at that moment:forge draining: /readyz answers 503 from now why=SIGTERM sshSessions=N. - A draining forge stays alive on/healthz. If liveness failed during a drain, kubelet would cut the SSH sessions and crew runs the drain is waiting for.src/http/server.ts: routes/healthzand/readyz(and HEAD for both).src/main.ts:shutdowncallsstartDrainingwith the signal it got. This replaces the oldforge drainingline, which never said why.charts/app/templates/deployment.yaml:- Readiness now asks
/readyzand stays quick. - Liveness moves from/to/healthz, withtimeoutSeconds: 10andfailureThreshold: 6. The landing page renders from the database and can take longer than the default 1-second timeout under load.docs/APPS.md,docs/ARCHITECTURE.md: now name both paths.Tests
test/health.test.tscovers:- a 200 ms blocking query (fake) still gets 200; - a database that throws gets 503, logged once, with recovery logged once; - a shutdown turns
/readyzinto 503 with its log line, and a second signal adds nothing; - end to end, a slowedapp.db.querystill gets 200 on/healthz, and a drain turns/readyzinto 503 while/healthzand/stay 200.readinessblock intest/web.test.tsasserted that/healthzanswers 503 during a drain. That is the meaning this issue removes, so the block moved intotest/health.test.tsunder the new meaning.tscis clean. The two server-level tests could not run here because the sandbox refuseslistenon a unix socket, which everyserve()test in the repo needs. The forge's check runs them.Not done
scripts/standby/values/trevato-apps--forge--prod.yaml) is in another repository. If it overrides the probes, it needs the same/readyzand liveness change there.forge-guard. The forge never answers 503 for it, so it needs no new log line. It still shows on/api/v1/health.Try it Boot with
bun run src/main.ts, then runcurl -i :3000/healthzandcurl -i :3000/readyz(both answer 200). Send SIGTERM:/readyzanswers 503 and the log has theforge draining … why=SIGTERMline.🤖 Generated with Claude Code
Closes #1221