Public: anyone can read this; the crew can change it.
Sentinel: release was degraded 611 minutes today: Release 0.7.374 is unproven: https://forge.open-platform.sh/explore as desktop /explore answered with an empty body (st… #9365
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/9358-sentinel-release-was-degraded-611-minute → main 1 commit fork
3 files
changed +24
−3
src/release/prove.ts +2 −2
| @@ -41,7 +41,7 @@ export interface Proof { | ||
| 41 | 41 | readonly since?: string; |
| 42 | 42 | /** How many proofs of this version have failed in a row; 1 when unset. */ |
| 43 | 43 | readonly failures?: number; |
| 44 | /** The first failing check was only a slow answer (`Failure.slow`); retried sooner. */ | |
| 44 | /** The first failing check was only a slow or empty answer (`Failure.slow`, `Failure.empty`); retried sooner (#9358). */ | |
| 45 | 45 | readonly slow?: boolean; |
| 46 | 46 | } |
| 47 | 47 | |
| @@ -160,7 +160,7 @@ export async function proveRelease(o: ProveOptions): Promise<Proof | undefined> | ||
| 160 | 160 | anonymousBoard: true, |
| 161 | 161 | heavy: { version: o.version, probe: ownProbeHeaders(o.dataDir), ...o.heavy }, ...(o.appHost ? { appHost: o.appHost } : {}), |
| 162 | 162 | }); |
| 163 | return finish(failures[0] === undefined ? undefined : checkLine(failures[0]), failures[0]?.slow === true); | |
| 163 | return finish(failures[0] === undefined ? undefined : checkLine(failures[0]), failures[0]?.slow === true || failures[0]?.empty === true); | |
| 164 | 164 | } catch (error) { |
| 165 | 165 | return finish(`the smoke checks could not run: ${String(error)}`); |
| 166 | 166 | } |
src/release/smoke.ts +6 −1
| @@ -83,6 +83,8 @@ export interface Failure { | ||
| 83 | 83 | why: string; |
| 84 | 84 | /** The check failed only because the answer was slower than allowed. */ |
| 85 | 85 | slow?: boolean; |
| 86 | /** The check failed only because a 200 answered with no body: a lost answer, retried like a slow one (#9358). */ | |
| 87 | empty?: boolean; | |
| 86 | 88 | } |
| 87 | 89 | |
| 88 | 90 | /** The first stylesheet href in a page, or null. */ |
| @@ -194,7 +196,10 @@ async function watch(root: URL, token: string, heavy: Heavy, fetch: typeof globa | ||
| 194 | 196 | return gone(`/healthz reported X-Forge-Version ${res.headers.get("x-forge-version") ?? "none"}, wanted ${heavy.version}`, res.status); |
| 195 | 197 | if (path === "/boards" && anonymousBoard && !token && isSignInRedirect(url, res)) return { failure: null, slow: false }; |
| 196 | 198 | if (res.status !== 200) return gone(`${path} expected 200`, res.status); |
| 197 | if (!body.trim()) return { ...gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status), empty: true }; | |
| 199 | if (!body.trim()) { | |
| 200 | const lost = gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status); | |
| 201 | return { failure: { ...lost.failure, empty: true }, slow: false, empty: true }; | |
| 202 | } | |
| 198 | 203 | return { failure: null, slow: false }; |
| 199 | 204 | } catch (error) { |
| 200 | 205 | return { ...gone(`${path} did not answer within ${slowMs * 2} ms (${error})`), hung: true }; |
test/release-prove.test.ts +16
| @@ -263,3 +263,19 @@ describe("a slow-only proof is asked again sooner (#9238)", () => { | ||
| 263 | 263 | } |
| 264 | 264 | }); |
| 265 | 265 | }); |
| 266 | ||
| 267 | describe("an empty-body proof is asked again sooner (#9358)", () => { | |
| 268 | test("a version whose /explore answers empty twice is due again after a minute, not five", async () => { | |
| 269 | const dir = mkdtempSync(join(tmpdir(), "forge-prove-")); | |
| 270 | try { | |
| 271 | const { fake } = site("5.0.0", { "/explore": () => new Response("", { status: 200 }) }); | |
| 272 | const p = await proveRelease({ dataDir: dir, version: "5.0.0", url: "https://forge.test", fetch: fake, sleep: async () => {}, heavy: { watchMs: 30, pollMs: 5 } }); | |
| 273 | expect(p?.ok).toBe(false); | |
| 274 | expect(p?.failure).toContain("empty body"); | |
| 275 | expect(p?.slow).toBe(true); | |
| 276 | expect(proofDue(p, "5.0.0", Date.parse(p!.at) + 61_000)).toBe(true); | |
| 277 | } finally { | |
| 278 | rmSync(dir, { recursive: true, force: true }); | |
| 279 | } | |
| 280 | }); | |
| 281 | }); |
new above
offline