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
Desktop / coding-agent clients that periodically call GET /v1/models (e.g. catalog refresh)
Description
Hourly models.dev periodic sync is a self-DoS on 3.8.49. Each successful sync invalidates pricing/catalog memoization; the next client catalog fetch rebuilds with the unmemoized N+1 path; the event loop sticks; the dashboard dies — on a timer.
Event loop stuck → /dashboard 502/timeout / 0-byte public responses; /healthz multi-second or timeout
Observed settings shape:
Key
Value
modelsDevSyncEnabled
true
modelsDevSyncInterval
3600000 (1 hour — schema minimum in settingsSchemas.ts)
models_dev_pricing rows
190
After we set modelsDevSyncEnabled=false, deleted the 190 rows, and restarted:
UI stayed in the 10–50 ms range
/healthz6–13 ms
No more hourly "OmniRoute is down" episodes tied to the sync cycle
This is not random flakiness. It is a scheduled invalidation of a path that is known to be CPU-hostile on the image operators are forced to run until a SemVer with #10055 exists (#10296).
Steps to Reproduce
Run diegosouzapw/omniroute:3.8.49 single process.
Enable models.dev sync with interval 1h (modelsDevSyncInterval=3600000) and allow an initial sync to populate models_dev_pricing (order of hundreds of provider keys / pricing rows is enough; we had 190).
Confirm catalog has been built once and is serving from memo.
Either wait for the periodic timer or invoke the same write path the timer uses (saveModelsDevPricing → invalidateDbCache("pricing")).
Immediately have a client call GET /v1/models (Desktop/Codex catalog refresh is enough).
Do not invalidate the live catalog on pricing sync until the new pricing data is memoized / safe to serve — or dual-buffer so readers never hit a cold unmemoized rebuild on the request thread.
Never run sync + full catalog rebuild on the request/event-loop thread that serves /dashboard and /healthz.
Default / minimum interval must not be 1 hour if each cycle can retrigger a 50s+ CPU loop on stable images; either raise the cost floor awareness, gate sync behind cheaper rebuilds, or refuse to ship intervals that schedule known multi-minute self-DoS on 3.8.49-class builds.
After a sync, the next GET /v1/models should not be a multi-tens-of-seconds event-loop stall for all other HTTP.
Related: #9685 (N+1), #10052 (healthz), #10055 (memoize merged, not in 3.8.49 image), #10296 (no SemVer with fix), dashboard catch-22 (settings unreachable during the cycle).
Ask (concrete):
Do not drop the live catalog on pricing sync until the new pricing is memoized and a warm entry can be published atomically.
Never couple sync write + full catalog rebuild to the request thread.
Revisit 1h as a default/minimum while stable images still pay multi-tens-of-seconds rebuilds.
Until fixed: document that enabling models.dev sync on 3.8.49 is an intentional periodic self-DoS under real catalog sizes.
No secrets, hostnames, tokens, or private combo names.
Validation Plan
Integration: after saveModelsDevPricing, concurrent /healthz and /dashboard stay fast; next /v1/models does not pin the loop for tens of seconds.
Assert invalidation strategy (dual-buffer / post-memoize publish) with unit tests around invalidateDbCache("pricing") vs catalog memo.
Soak with 1h timer accelerated (e.g. short interval in test) over many cycles without UI multi-second freezes on a 3.8.49-equivalent builder cost (or with memoize + large synthetic builder delay).
OmniRoute Version
3.8.49 (official image
diegosouzapw/omniroute:3.8.49)Installation Method
Docker / Docker Compose
Operating System
Linux
OS Version
Linux (Kubernetes single replica)
Node.js Version
Image default (official Docker image)
Provider(s) Involved
models.dev sync (affects all clients of
GET /v1/models)Model(s) Involved
N/A (row count / N+1 shape matters; we observed 190
models_dev_pricingentries)Client Tool
Desktop / coding-agent clients that periodically call
GET /v1/models(e.g. catalog refresh)Description
Hourly models.dev periodic sync is a self-DoS on 3.8.49. Each successful sync invalidates pricing/catalog memoization; the next client catalog fetch rebuilds with the unmemoized N+1 path; the event loop sticks; the dashboard dies — on a timer.
Sequence (stock code):
startPeriodicSync/ periodic timer (src/lib/modelsDevSync.ts)saveModelsDevPricing(...)saveModelsDevPricingcallsinvalidateDbCache("pricing")(same file ~222–236)dropCatalogCacheIfStateChanged()insrc/app/api/v1/models/catalogCache.tscurlGET /v1/modelsruns unmemoizedgetModelsDevPricingper model on 3.8.49 (fix(api): GET /v1/models pegs event loop — getModelsDevPricing re-reads per model #9685)/dashboard502/timeout / 0-byte public responses;/healthzmulti-second or timeoutObserved settings shape:
modelsDevSyncEnabledtruemodelsDevSyncIntervalsettingsSchemas.ts)models_dev_pricingrowsAfter we set
modelsDevSyncEnabled=false, deleted the 190 rows, and restarted:/healthz6–13 msThis is not random flakiness. It is a scheduled invalidation of a path that is known to be CPU-hostile on the image operators are forced to run until a SemVer with #10055 exists (#10296).
Steps to Reproduce
diegosouzapw/omniroute:3.8.49single process.modelsDevSyncInterval=3600000) and allow an initial sync to populatemodels_dev_pricing(order of hundreds of provider keys / pricing rows is enough; we had 190).saveModelsDevPricing→invalidateDbCache("pricing")).GET /v1/models(Desktop/Codex catalog refresh is enough)./dashboardand/healthz:Expected Behavior
/dashboardand/healthz.GET /v1/modelsshould not be a multi-tens-of-seconds event-loop stall for all other HTTP.Actual Behavior
models_dev_pricing,invalidateDbCache("pricing")GET /v1/modelsThe gateway schedules its own outage window.
Test Impact
Needs a new integration test
Error Logs / Output
Additional Context
Related: #9685 (N+1), #10052 (healthz), #10055 (memoize merged, not in 3.8.49 image), #10296 (no SemVer with fix), dashboard catch-22 (settings unreachable during the cycle).
Ask (concrete):
No secrets, hostnames, tokens, or private combo names.
Validation Plan
saveModelsDevPricing, concurrent/healthzand/dashboardstay fast; next/v1/modelsdoes not pin the loop for tens of seconds.invalidateDbCache("pricing")vs catalog memo.