fix(ci): the runner's healthcheck had the same lie as the broker's #5604
2 changed files+60−1
Modifiedsrc/__tests__/ci-runner-sidecar.test.ts+35−0View fileUnifiedSplit
@@ -101,6 +101,41 @@ describe("the runner container holds nothing worth stealing", () => {
101101 });
102102});
103103
104describe("the runner cannot report healthy while unable to work", () => {
105 /**
106 * The broker beside this one shipped on 2026-08-31 reporting `healthy`
107 * while unable to reach the docker socket at all — its /healthz returned a
108 * constant "ok". This agent had the same shape: its one external dependency
109 * is the shared checkout volume, and without it every step fails with "the
110 * ci-work volume must be mounted at the same path on both services".
111 *
112 * When a service has exactly one dependency, "is it up" and "can it reach
113 * that dependency" are the same question. Answering the easier one is how a
114 * deploy goes green while the feature is dead.
115 */
116 test("healthz checks the shared volume, and says what is wrong", () => {
117 const health = agentSrc.slice(
118 agentSrc.indexOf('url.pathname === "/healthz"'),
119 agentSrc.indexOf("if (!authorized(req))")
120 );
121 expect(health).toContain("GLUECRON_CI_WORKDIR");
122 expect(health).toContain("existsSync");
123 expect(health).toContain("503");
124 expect(health).toContain("mounted at the same path");
125 });
126
127 test("an unconfigured workdir is not reported as broken", () => {
128 // A dev machine runs this agent with no volume at all. Reporting that as
129 // unhealthy would train people to ignore the check, which costs more than
130 // the check is worth.
131 const health = agentSrc.slice(
132 agentSrc.indexOf('url.pathname === "/healthz"'),
133 agentSrc.indexOf("if (!authorized(req))")
134 );
135 expect(health).toContain("workdir &&");
136 });
137});
138
104139describe("a runner we cannot reach is our failure, not the user's", () => {
105140 const req = { runId: "r1", run: "echo hi", cwd: "/ci-work/x", env: {}, timeoutMs: 1000 };
106141
Modifiedsrc/runner-agent.ts+25−1View fileUnifiedSplit
@@ -175,7 +175,31 @@ const server = Bun.serve({
175175 const url = new URL(req.url);
176176
177177 // Unauthenticated: liveness only, so docker's healthcheck needs no secret.
178 if (url.pathname === "/healthz") return new Response("ok");
178 if (url.pathname === "/healthz") {
179 // READINESS, not a constant. This runner's only external dependency is
180 // the shared checkout volume: without it every step fails with "the
181 // ci-work volume must be mounted at the same path on both services",
182 // and a container reporting healthy while that is true is a green light
183 // over a service that cannot work.
184 //
185 // The broker beside this one shipped with exactly that bug on
186 // 2026-08-31 and came up "healthy" while unable to reach docker at all.
187 // Same shape, same fix: when a service has one dependency, "is it up"
188 // and "can it reach that dependency" are the same question, and
189 // answering the easier one is how a deploy passes while the feature is
190 // dead.
191 //
192 // Checked only when a workdir is configured, so a dev machine running
193 // this agent without the volume is not reported as broken.
194 const workdir = process.env["GLUECRON_CI_WORKDIR"] ?? "";
195 if (workdir && !existsSync(workdir)) {
196 return new Response(
197 `not ready: ${workdir} is not visible to the runner — the ci-work volume must be mounted at the same path on the app and the runner`,
198 { status: 503 }
199 );
200 }
201 return new Response("ok");
202 }
179203
180204 if (!authorized(req)) return json({ error: "unauthorized" }, 401);
181205
182206
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts