You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf: xum server first load spends ~160 ms evaluating the entry module graph (zod schema construction) #6071
On the first load of xum server in a browser, one main-thread task in main-*.js takes about 160 ms on desktop. Lighthouse reports it as the largest Total Blocking Time (TBT) contributor. The task is the browser evaluating the entry ES module graph. It is not React render or oRPC work.
About half of the task (95.6 of 190 ms in a profiled run) is zod schema construction at import time.
Why Lighthouse shows two "modes"
The Lighthouse auditor sees the task at either ~80 ms ("fast") or ~160 ms ("slow"). This is a Lantern simulation artifact. Lantern counts a task that contains a Layout at x0.5. The Layout comes from the index.html "Loading Xum..." ellipsis animation. The slow value is the real cost.
Plan
perf: update zod to 4.6.5: lockfile plus a package.json floor of ^4.6.5. Measured eval median 160 -> 93 ms. Measured desktop TBT 111 -> 49.5 ms, mobile 659 -> 454 ms. This PR needs a changelog audit (4.4.3 -> 4.6.5) and a check that provider-facing tool JSON Schemas and oRPC schemas do not change, or that each change is explained.
perf: import schema values from leaf modules, not the orpc barrel: 6 browser imports of @/common/orpc/schemas pull schemas/api.ts into the main chunk. Replace them with leaf imports and add an ESLint guard. Measured eval median 93 -> 77 ms.
Gate
Local: interleaved base/head Lighthouse blocks. Head eval median at least 50 ms below base (PR 1) and 10 ms below (PR 2). Every head run under 120 ms desktop and 480 ms mobile.
Post-merge: at least 5 auditor cycles. The longest main-*.js task stays under 120 ms desktop and 480 ms mobile in every qualified run. Host qualification (CPU pressure, load) is declared from an A/A run before the candidate runs. A run that misses with clean host data fails the gate.
Desktop cold start A/B (scripts/perf/desktopColdStart.ts, 20 pairs): one-sided upper bound at most +5%.
zod 4.6.5 is on main: #6078 merged as 40de096e6b, after its prerequisites #6080 and #6087.
In the Lighthouse branch runs (3 heads, 12 runs each), the longest startup task was under its limit in every run: desktop 77–90 ms, mobile 306–354 ms. On main, all 12 runs were over.
D2 on the final head (run 38086092991): mean −5.17%, upper bound −4.48%.
First-run mobile Performance: 65–67, against 54–56 on main.
This issue stays open until the post-merge gate passes: 5 auditor main cycles in which every host-qualified run is under 120 ms desktop and 480 ms mobile. Next after that: the leaf imports that replace the @/common/orpc/schemas barrel (plan estimate: 77 ms). Follow-ups: #6091.
After #6078 (zod 4.6.5, 40de096e6b), the longest main-*.js startup task stayed under its limit in every host-qualified Lighthouse run in 5 main cycles. I read each cycle's record from the auditor's run history myself.
60 of 60 host-qualified runs passed. Before the bump, 12 of 12 runs were over on main (mobile 574–674 ms, desktop 140–170 ms).
The host was under heavy load in cycle 4 (1-minute load average 16–61 on 32 CPUs). Mobile still had 66 ms of margin there. Desktop's smallest margin was 34 ms.
Cycles 2 and 4 measured an unchanged main again. The gate I set before any results did not require new commits.
Lighthouse first-run mobile Performance moved from 54–56 to 62–65, and seeded mobile from 49–52 to 56–57. Desktop is 94–96.
Not done here: the planned second step, replacing imports of the @/common/orpc/schemas barrel with leaf imports (plan estimate: the task drops to about 77 ms). The task already meets its limit, so this step is optional. I track it with the mobile work instead of keeping this issue open. Follow-ups of the bump are in #6091.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $704.04
Post-gate observation (perf owner). This issue stays closed.
In auditor main cycle 20261011T113213Z (main 90974719ff, setup v2), one host-qualified run went over the startup-task limit. Seeded mobile run 3 had its longest main-*.js task at 649 ms (limit 480). I read that run's report file myself:
Seeded mobile run
Longest main task
TBT
CLS
Lighthouse benchmarkIndex
1
254 ms
359 ms
0.001
2,834
2
279 ms
408 ms
0.001
3,122
3
649 ms
1,114 ms
0.001
2,050
The other 11 runs of that cycle stayed under the limits (mobile 254–322 ms, desktop 64–73 ms). Run 3's CPU benchmark was about 30% below runs 1–2 (Lighthouse then applies its 4x throttle on top). The auditor's retry floor is 2,000, so this run counted as qualified. I read it as a slow-host outlier, not a product regression. Nothing in the startup path changed in this commit range.
Reopen trigger: reopen this issue if a second host-qualified run goes over the limit within the next 10 main cycles, or if any over-limit run has a benchmarkIndex of 2,500 or more. The gate rule itself is unchanged. I am not amending it after the fact.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $752.79
Post-gate watch ended clean. No qualified run went over the limit in the 10 main cycles after the 649 ms outlier in run 20261011T113213Z, so the reopen trigger did not fire. This issue stays closed.
Limits: the longest main-*.js task must stay under 120 ms on desktop and 480 ms on mobile. Each cycle is 12 Lighthouse runs (setup v2-sw-disabled).
* The report folder of the first cycle was pruned by the auditor's run script, which kept only the 10 newest run folders. Its task durations come from the history row that the script wrote from the report files at cycle time (states.<page>.<preset>.attribution[].main_task_ms). All 12 runs were host-qualified. The script no longer prunes run folders. I read the other 9 cycles from their report files.
One run had a benchmarkIndex below 2,500: seeded mobile in 20261011T190449Z, at 2,490. Its longest task was 255 ms, under the mobile limit.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $772.64
Reopening under the reopen trigger I declared ("an over-limit run with benchmarkIndex ≥ 2500"). The trigger fired, and I am not changing it after the number.
The run: auditor run 20261012T112644Z on main d8defc5d67. In the first-run page, mobile, run 2, the longest first-party task was 1,152 ms (API-*.js, limit 480 ms), with benchmarkIndex 2,840.
Why I think it is host contention (not verified):
In the same run, the host recorded loadavg-1 68.0 (32 CPUs) and PSI cpu some avg10 81.0.
The run had TBT 6,836 ms, with four more long tasks of 1.2–1.9 s that were "Unattributable".
Runs 1 and 3 of the same cell had 236 ms and 181 ms.
What I called "host-qualified" runs only means the measurement lock was acquired without a timeout. The auditor's load guard waits up to 5 minutes for load below 0.75 × nproc, and then runs anyway. So "qualified" never excluded a loaded host.
24 of 420 runs since the gate started began with load ≥ 24 or PSI ≥ 20.
The gate result still stands: every gate run was under the limit, including the loaded ones. Load can only add time.
Close condition, declared now and before any new number:
Re-run the first-run mobile cell 6 times on current main.
Each run must start and end on a quiet host: loadavg-1 < 24 and PSI some avg10 < 20, both at start and at end. A run that fails this is discarded and retried, at most 12 tries in total.
If all 6 quiet runs stay under 480 ms, I close this issue as host contention.
If any quiet run goes over 480 ms, this is a regression and I investigate it here.
Going forward: a run counts toward any startup-task verdict only if it meets that quiet-host condition. Runs that fail it are still recorded, but they are marked unqualified.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $826.43
Closing as host contention, under the close condition declared above. I read the reports myself.
The quiet-host re-check ran on main d8defc5d67, the same commit as the over-limit run. It is in the auditor's folder runs-quiet/20261012T115151Z. Setup: first-run page, mobile, Lighthouse 13.5.0, setup v2, service worker off in every try.
Try
Longest first-party task
Chunk
benchmarkIndex
Start load / PSI
End load / PSI
1
151 ms
API
3,023
9.19 / 1.88
8.61 / 0.84
2
154 ms
API
2,974
8.61 / 0.69
8.29 / 0.31
3
171 ms
API
2,925
8.29 / 0.31
8.09 / 0.13
4
150 ms
API
2,976
8.09 / 0.13
7.53 / 0.05
5
168 ms
API
3,084
7.53 / 0.05
7.17 / 0.02
6
158 ms
API
3,162
7.17 / 0.02
7.50 / 0.00
All 6 counted tries were on a quiet host at start and at end (load < 24, PSI < 20), and all stayed under 480 ms. The maximum was 171 ms. So the 1,152 ms run was host contention (load 68, PSI 81).
Rule change, in effect from now on: the auditor now records a quiet_host flag for every run (start and end both quiet). Only runs with that flag can trigger the over-limit rule. The load guard no longer runs on a loaded host: it records the slot as skipped instead. The reopen trigger stays as declared, now evaluated on quiet-host runs only.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $832.91
Activity
🤖 fix: keep a code-point-safe prefix in streamed history evidence
🤖 fix: rewrite only subschemas, and keep an existing anyOf beside the…
🤖 perf: update zod to 4.6.5
🤖 fix: keep tuple result types exact in code_execution declarations
🤖 tests: pin persisted-data parsing across the zod 4.6 changes
🤖 fix: fail history rows with a strict-object __proto__ key, as zod 4…
🤖 fix: keep a code-point-safe prefix in streamed history evidence
🤖 fix: keep minute-precision workflow timestamps readable under zod 4.6
🤖 perf: update zod to 4.6.5
🤖 fix: keep tuple result types exact in code_execution declarations
🤖 tests: pin persisted-data parsing across the zod 4.6 changes
🤖 fix: fail history rows with a strict-object __proto__ key, as zod 4…
🤖 fix: keep a code-point-safe prefix in streamed history evidence
🤖 fix: keep minute-precision workflow timestamps readable under zod 4.6
ThomasK33 commentedon Oct 10, 2026
zod 4.6.5 is on main: #6078 merged as
40de096e6b, after its prerequisites #6080 and #6087.This issue stays open until the post-merge gate passes: 5 auditor main cycles in which every host-qualified run is under 120 ms desktop and 480 ms mobile. Next after that: the leaf imports that replace the
@/common/orpc/schemasbarrel (plan estimate: 77 ms). Follow-ups: #6091.ThomasK33 commentedon Oct 11, 2026
Post-merge gate passed. I am closing this issue.
After #6078 (zod 4.6.5,
40de096e6b), the longestmain-*.jsstartup task stayed under its limit in every host-qualified Lighthouse run in 5 main cycles. I read each cycle's record from the auditor's run history myself.Not done here: the planned second step, replacing imports of the
@/common/orpc/schemasbarrel with leaf imports (plan estimate: the task drops to about 77 ms). The task already meets its limit, so this step is optional. I track it with the mobile work instead of keeping this issue open. Follow-ups of the bump are in #6091.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$704.04ThomasK33 commentedon Oct 11, 2026
Post-gate observation (perf owner). This issue stays closed.
In auditor main cycle
20261011T113213Z(main90974719ff, setup v2), one host-qualified run went over the startup-task limit. Seeded mobile run 3 had its longestmain-*.jstask at 649 ms (limit 480). I read that run's report file myself:The other 11 runs of that cycle stayed under the limits (mobile 254–322 ms, desktop 64–73 ms). Run 3's CPU benchmark was about 30% below runs 1–2 (Lighthouse then applies its 4x throttle on top). The auditor's retry floor is 2,000, so this run counted as qualified. I read it as a slow-host outlier, not a product regression. Nothing in the startup path changed in this commit range.
Reopen trigger: reopen this issue if a second host-qualified run goes over the limit within the next 10 main cycles, or if any over-limit run has a benchmarkIndex of 2,500 or more. The gate rule itself is unchanged. I am not amending it after the fact.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$752.79🤖 fix: send nullable tool fields to Ollama as anyOf instead of type a…
🤖 fix: send nullable tool fields as anyOf on custom openai-compatible…
🤖 perf: update zod to 4.6.5 (coder#6078)
ThomasK33 commentedon Oct 11, 2026
Post-gate watch ended clean. No qualified run went over the limit in the 10 main cycles after the 649 ms outlier in run 20261011T113213Z, so the reopen trigger did not fire. This issue stays closed.
Limits: the longest
main-*.jstask must stay under 120 ms on desktop and 480 ms on mobile. Each cycle is 12 Lighthouse runs (setup v2-sw-disabled).* The report folder of the first cycle was pruned by the auditor's run script, which kept only the 10 newest run folders. Its task durations come from the history row that the script wrote from the report files at cycle time (
states.<page>.<preset>.attribution[].main_task_ms). All 12 runs were host-qualified. The script no longer prunes run folders. I read the other 9 cycles from their report files.One run had a benchmarkIndex below 2,500: seeded mobile in 20261011T190449Z, at 2,490. Its longest task was 255 ms, under the mobile limit.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$772.64ThomasK33 commentedon Oct 12, 2026
Reopening under the reopen trigger I declared ("an over-limit run with benchmarkIndex ≥ 2500"). The trigger fired, and I am not changing it after the number.
The run: auditor run 20261012T112644Z on main
d8defc5d67. In the first-run page, mobile, run 2, the longest first-party task was 1,152 ms (API-*.js, limit 480 ms), with benchmarkIndex 2,840.Why I think it is host contention (not verified):
some avg1081.0.artifactBridge.ts, a lazy Artifacts tab). Served first-load JS moved by +38 B.A correction to my earlier record on this issue:
Close condition, declared now and before any new number:
some avg10< 20, both at start and at end. A run that fails this is discarded and retried, at most 12 tries in total.Going forward: a run counts toward any startup-task verdict only if it meets that quiet-host condition. Runs that fail it are still recorded, but they are marked unqualified.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$826.43ThomasK33 commentedon Oct 12, 2026
Closing as host contention, under the close condition declared above. I read the reports myself.
The quiet-host re-check ran on main
d8defc5d67, the same commit as the over-limit run. It is in the auditor's folderruns-quiet/20261012T115151Z. Setup: first-run page, mobile, Lighthouse 13.5.0, setup v2, service worker off in every try.All 6 counted tries were on a quiet host at start and at end (load < 24, PSI < 20), and all stayed under 480 ms. The maximum was 171 ms. So the 1,152 ms run was host contention (load 68, PSI 81).
Rule change, in effect from now on: the auditor now records a
quiet_hostflag for every run (start and end both quiet). Only runs with that flag can trigger the over-limit rule. The load guard no longer runs on a loaded host: it records the slot as skipped instead. The reopen trigger stays as declared, now evaluated on quiet-host runs only.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$832.91