From c549ce1734d2beaffd7528ba105409086afe7a8c Mon Sep 17 00:00:00 2001 From: AminDhouib Date: Wed, 9 Sep 2026 06:13:00 -0400 Subject: [PATCH 1/4] fix(ci): re-base the smoke optional-chunk budget for libheif-js 1.23.2 The package-smoke consumer installs fresh from core's `libheif-js: ^1.19.8` with no lockfile - deliberately, so it resolves the way a real consumer would. libheif-js 1.23.2 published 2026-09-05T19:18Z and grew the opt-in HEIC emscripten bundle from 1,459 kB to 1939.4 KiB, so the very next nightly (2026-09-06) went red on a budget no repo change had touched, and every nightly since has died the same way: Error: Consumer JS asset libheif-bundle-DeNY_ZvL.js is 1939.4 KiB, above 1600.0 KiB The same job gates PRs through e2e.yml. No PR ran between the upstream publish and 2026-09-09, which is the only reason PRs still looked green; today's run fails on this job and nothing else. This ceiling guards the OPT-IN chunk, which is upstream WASM we neither author nor can shrink. Core's mandatory path keeps its own tighter guards (consumerEntryChunkBudget, .size-limit.json), so re-basing here does not weaken them. Vite's chunkSizeWarningLimit moves in step. --- scripts/package-smoke-consumer.mjs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/package-smoke-consumer.mjs b/scripts/package-smoke-consumer.mjs index 4edde1da..ffff6546 100644 --- a/scripts/package-smoke-consumer.mjs +++ b/scripts/package-smoke-consumer.mjs @@ -38,7 +38,15 @@ const kib = 1024 // (+~30) plus margin; anything larger must be a deliberate re-base, not a // bump-to-green. Whether locales belong on the mandatory path is deferred (F-701). const consumerEntryChunkBudget = 560 * kib -const consumerOptionalChunkBudget = 1600 * kib +// The optional (opt-in HEIC) chunk is libheif-js's emscripten bundle — upstream +// WASM we do not author and cannot shrink. Re-based 1600→2100 KiB 2026-09-09: +// libheif-js 1.23.2 (published 2026-09-05) grew it from ~1425 KiB to 1939.4 KiB, +// and the consumer installs fresh from core's `^1.19.8` range with no lockfile +// (a real consumer gets the same resolution), so the nightly went red the very +// next run with no repo change. This budget guards the OPT-IN path only — +// core's mandatory path is guarded by `consumerEntryChunkBudget` above and by +// `.size-limit.json`. Keep the vite `chunkSizeWarningLimit` below in step. +const consumerOptionalChunkBudget = 2100 * kib function assertInsideRepo(target) { const rel = relative(repoRoot, resolve(target)) @@ -396,7 +404,7 @@ export default defineConfig({ }, build: { manifest: true, - chunkSizeWarningLimit: 1600, + chunkSizeWarningLimit: 2100, rollupOptions: { output: { manualChunks(id) { From d69763d25cab3a0e06851513135536b3f5c84612 Mon Sep 17 00:00:00 2001 From: AminDhouib Date: Wed, 9 Sep 2026 06:15:38 -0400 Subject: [PATCH 2/4] test(server): give the live drive suite a network-shaped timeout Identical failure on the 2026-08-26, 09-02, 09-04, 09-05 and 09-06 nightlies - the same single test every time, with the other 24 assertions green: FAIL tests/integration/drive-clients-live.integration.test.ts > drive-clients live - one-drive > a client can download each fixture and the bytes are byte-exact ... Error: Test timed out in 5000ms. Every test in this file is network-bound against a production cloud API, and the download case costs a folder listing plus a redirect-followed fetch and a full stream drain per fixture. Vitest's 5 s default is a unit-test budget that nobody chose for this suite, so the gate was reporting the clock rather than the code. What the suite actually gates is byte-integrity - sha256 against the committed fixture - and never latency, so a 30 s ceiling loses nothing: a provider that is down, or a token that is broken, still goes RED. hookTimeout moves with it because beforeAll's OAuth mint is the same kind of round trip. vi.setConfig applies file-wide and keeps the diff at 13 lines; restructuring the describe call to take a timeout argument would have re-indented the whole suite body for no additional guarantee. Verified rather than assumed: a throwaway spec sleeping 7 s passes under vi.setConfig({ testTimeout: 20_000 }) where the 5 s default fails it. --- .../drive-clients-live.integration.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/server/tests/integration/drive-clients-live.integration.test.ts b/packages/server/tests/integration/drive-clients-live.integration.test.ts index 1e9a2564..a6fb9aa3 100644 --- a/packages/server/tests/integration/drive-clients-live.integration.test.ts +++ b/packages/server/tests/integration/drive-clients-live.integration.test.ts @@ -13,7 +13,7 @@ // configured but whose token is broken does NOT skip — minting happens in // beforeAll, so a bad token throws there and the suite goes RED (configured but // broken is a real signal, never silently swallowed). -import { describe, it, expect, beforeAll } from 'vitest' +import { describe, it, expect, beforeAll, vi } from 'vitest' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' @@ -32,6 +32,18 @@ type ProviderSlug = (typeof PROVIDERS)[number] const SANDBOX_ON = process.env.UPUP_DRIVE_SANDBOX === '1' +// Every test here is network-bound against a PRODUCTION cloud API, and the +// download case costs a folder listing plus a redirect-followed fetch and a full +// stream drain per fixture. Vitest's 5 s default is a unit-test budget nobody +// chose for this suite: under it the one-drive download test timed out on the +// 2026-08-26, 09-02, 09-04, 09-05 and 09-06 nightlies while the other 24 +// assertions passed every time — a red gate reporting the clock, not the code. +// What this suite gates is byte-integrity (sha256 vs the committed fixture) and +// never latency, so give it a ceiling a real network deserves; a provider that +// is genuinely down or broken still goes RED, just later. hookTimeout covers +// beforeAll's OAuth token mint, which is the same kind of round trip. +vi.setConfig({ testTimeout: 30_000, hookTimeout: 30_000 }) + // ── Env plumbing ───────────────────────────────────────────────────────── function env(name: string): string | undefined { From 94f4e5d202beeabed3a4294e6393535eba0e637c Mon Sep 17 00:00:00 2001 From: AminDhouib Date: Wed, 9 Sep 2026 06:17:07 -0400 Subject: [PATCH 3/4] test(e2e): move the resume harness ports out of the ephemeral range The 2026-09-06 nightly's Full E2E job died here: Error: listen EADDRINUSE: address already in use :::53062 Error: presign harness never bound http://localhost:53062 - is @useupup/server built? Linux draws ephemeral source ports from 32768-60999, so 53061 and 53062 - the fixed LISTEN ports the two real-MinIO resume specs own - sit inside the window the kernel hands out for the runner's own outbound connections. Losing that race costs the whole job: the child dies immediately and the harness poll then burns its full 60 s against a port nothing is bound to. Moved to 31061 and 31062, below the range, and recorded the rule on startPresignHarness so the next spec that needs a harness picks a safe port instead of the next number up. The rest of the 5306x family is deliberately untouched: :53060 is the cross-framework gate's harness and sits inside the OAuth clients' registered redirect range (53050-53060), so it is pinned by external registration, not by convention. --- .../e2e/multipart-crash-restore-regressions.spec.ts | 8 +++++--- apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts | 7 ++++++- apps/e2e-test/e2e/multipart-resume-harness.ts | 4 ++++ apps/e2e-test/src/main.tsx | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts b/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts index 2ce93d5b..01a888eb 100644 --- a/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts +++ b/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts @@ -52,10 +52,12 @@ import { announceMinioSkip('multipart-crash-restore-e2e') -// Its own port: the sibling spec owns 53061, and two spec files must never -// contend for one harness even when Playwright raises the worker count. +// Its own port: the sibling spec owns 31061, and two spec files must never +// contend for one harness even when Playwright raises the worker count. Both +// sit below 32768 deliberately — see that spec for why the old 5306x pair was +// losing races against the runner's own ephemeral source ports. const HARNESS_PORT = Number( - process.env.UPUP_E2E_CRASH_RESTORE_SERVER_PORT ?? 53062, + process.env.UPUP_E2E_CRASH_RESTORE_SERVER_PORT ?? 31062, ) const HARNESS_URL = `http://localhost:${HARNESS_PORT}` const SCENARIO = `/?scenario=multipart-resume&server=${encodeURIComponent(HARNESS_URL)}` diff --git a/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts b/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts index 8c03c58f..d9eebb44 100644 --- a/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts +++ b/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts @@ -61,7 +61,12 @@ const SOURCE_BYTES = minioReady : Buffer.alloc(0) const SOURCE_SHA256 = sha256(SOURCE_BYTES) -const HARNESS_PORT = Number(process.env.UPUP_E2E_RESUME_SERVER_PORT ?? 53061) +// 31061, not 53061: Linux hands out ephemeral source ports from 32768-60999, +// so a fixed LISTEN port inside that window can be transiently held by one of +// the runner's own outbound connections. That is exactly how the 2026-09-06 +// nightly died — `EADDRINUSE :::53062` on the sibling spec, then 60 s of +// polling a harness that never bound. Below 32768 the kernel never competes. +const HARNESS_PORT = Number(process.env.UPUP_E2E_RESUME_SERVER_PORT ?? 31061) const HARNESS_URL = `http://localhost:${HARNESS_PORT}` let harness: PresignHarness | null = null diff --git a/apps/e2e-test/e2e/multipart-resume-harness.ts b/apps/e2e-test/e2e/multipart-resume-harness.ts index e3475378..fb6dedbb 100644 --- a/apps/e2e-test/e2e/multipart-resume-harness.ts +++ b/apps/e2e-test/e2e/multipart-resume-harness.ts @@ -101,6 +101,10 @@ export interface PresignHarness { * Deliberately NOT :53060 — the cross-framework gate's harness owns that port * and allows only the six storybook origins, so reusing it would 403 the app. * Each real-MinIO spec file owns its own port so two files can never contend. + * Pick that port BELOW 32768: Linux draws ephemeral source ports from + * 32768-60999, so a fixed listen port in that window loses races against the + * runner's own outbound connections (`EADDRINUSE`, then a 60 s poll against a + * harness that never bound — the 2026-09-06 nightly). */ export async function startPresignHarness( port: number, diff --git a/apps/e2e-test/src/main.tsx b/apps/e2e-test/src/main.tsx index 6247aaf3..283fc9a0 100644 --- a/apps/e2e-test/src/main.tsx +++ b/apps/e2e-test/src/main.tsx @@ -97,7 +97,7 @@ function RestrictionsDemo() { /** * Server-mode multipart scenario — loaded via - * `/?scenario=multipart-resume&server=http://localhost:53061`. + * `/?scenario=multipart-resume&server=http://localhost:31061`. * * The only story in this app that talks to a REAL @useupup/server + MinIO: * multipart at a 5 MiB threshold/part size with crash recovery on, which is @@ -163,7 +163,7 @@ function App() { ) : ( From fc98a023a7dec0551227c66141f9970a253dcedc Mon Sep 17 00:00:00 2001 From: AminDhouib Date: Wed, 9 Sep 2026 07:19:14 -0400 Subject: [PATCH 4/4] test(landing): force PostHog Query API recalculation in the ingestion poll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly "AI thumbs events landed" case failed 8 of the last 9 nights with a 90 s poll timeout, while PostHog's own `created_at` shows both `ai_response_rated` and `ai_response_feedback_comment` ingested 1-2 s after capture on every one of those nights (run ids e2e:1788769131541-…, e2e:1788594082953-…, e2e:1788509140023-…, …). The data was there for the whole poll; the poll never saw it. Cause: the Query API's default execution mode ("blocking") serves a cached result whenever one exists for identical query text and is not yet stale, and every iteration of `expect.poll` sends byte-identical text. A first poll that lands before the events are queryable caches an empty result and the remaining 90 s replay that cache. The support case right before it passes because its events are already minutes old by the time it polls. Send `refresh: 'force_blocking'` so every poll recalculates, and log a transient non-OK status so a future timeout is never a silent mystery. --- .../landing/ingestion-verification.spec.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/e2e-test/landing/ingestion-verification.spec.ts b/apps/e2e-test/landing/ingestion-verification.spec.ts index e25ec98e..5a6360ea 100644 --- a/apps/e2e-test/landing/ingestion-verification.spec.ts +++ b/apps/e2e-test/landing/ingestion-verification.spec.ts @@ -33,6 +33,16 @@ if (!queryKey) { * (array-of-arrays, column order = SELECT order). An auth/bad-request failure * throws (RED — an invalid key must never look like a pass); a transient 5xx / * network error returns null so the caller's poll can retry. + * + * `refresh: 'force_blocking'` is load-bearing. The Query API's default mode + * ("blocking") serves a CACHED result whenever one exists for the identical + * query text and is not yet stale — and every iteration of a poll below sends + * byte-identical text. So a first poll that runs before the events are visible + * caches an empty result, and the remaining 90 s replay that cache. That is + * exactly how the nightly "AI thumbs events landed" case failed 8 nights out + * of 9 (2026-08-26 → 09-07): PostHog's own `created_at` shows both events + * ingested 1-2 s after capture, i.e. present for the whole poll — the poll just + * kept reading the stale cache. `force_blocking` recalculates on every call. */ async function runHogql(query: string): Promise { let res: Response @@ -43,7 +53,10 @@ async function runHogql(query: string): Promise { authorization: `Bearer ${queryKey}`, 'content-type': 'application/json', }, - body: JSON.stringify({ query: { kind: 'HogQLQuery', query } }), + body: JSON.stringify({ + query: { kind: 'HogQLQuery', query }, + refresh: 'force_blocking', + }), }) } catch { return null // network blip — let the poll retry @@ -58,7 +71,15 @@ async function runHogql(query: string): Promise { `PostHog Query API rejected the query (HTTP 400): ${await res.text()}`, ) } - if (!res.ok) return null // transient upstream error — retry + if (!res.ok) { + // Transient upstream error — retry, but leave a trace in the job log + // so a poll that times out is never a silent mystery. + // eslint-disable-next-line no-console + console.log( + `[ingestion] PostHog Query API HTTP ${res.status} — retrying`, + ) + return null + } const json = (await res.json()) as { results?: unknown[][] } return json.results ?? [] }