fix(api): reconnect the /v1/models background-refresh scheduler (#11551) - #11574
Merged
diegosouzapw merged 2 commits intoAug 26, 2026
Merged
diegosouzapw merged 2 commits into
diegosouzapw merged 2 commits into
Conversation
…osouzapw#11551) `route.ts` has been passing a third argument since diegosouzapw#10198: getUnifiedModelsResponse(request, {}, { scheduleBackgroundRefresh: (task) => after(task) }) but diegosouzapw#9199 removed the injection point, leaving `getUnifiedModelsResponse` with two parameters. The object was silently dropped, and `catalogCache.ts` kept scheduling the stale-while-revalidate rebuild with `setTimeout(…, 0)`. That is not equivalent. The builder is overwhelmingly synchronous under the single-threaded App Router, so a rebuild that starts one macrotask later still pins the event loop before the stale response has been flushed — the client waits out the whole rebuild it was supposed to be spared. Next's `after()` runs the task only after the flush, which is the guarantee diegosouzapw#8728 specified and the two integration tests in `tests/integration/v1-models-swr-response-flush-8728.test.ts` describe. - `catalogCache.ts` imports `after` and schedules through `defaultBackgroundRefreshScheduler`, falling back to the macrotask outside a Next request scope (CLI/Electron server, unit tests) where there is no response to flush. - `resolveCachedCatalogResponse` accepts the scheduler as a per-call option, alongside a per-call stale-window override for tests. Neither is the module-level policy accessor diegosouzapw#9199 removed: no setter, no module state, no production caller — the 30 s bound still holds for every real request. - `getUnifiedModelsResponse` takes the options object again and forwards it, so the route's `after()` wiring is live instead of dead. No behavior change for callers that pass nothing; both integration tests go green without being edited.
5 tasks done
diegosouzapw
merged commit Aug 26, 2026
b61a530
into
diegosouzapw:release/v3.8.51
10 of 16 checks passed
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…osouzapw#11551) (diegosouzapw#11574) Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11551.
The dead wire
src/app/api/v1/models/route.tspasses a third argument:getUnifiedModelsResponse(catalog.ts:159) declares two parameters. The object wassilently dropped, and
catalogCache.tsnever importedafter— it kept scheduling thestale-while-revalidate rebuild with
setTimeout(…, 0).Confirming the issue's open question about why
tscdid not flag the extra argument:tsconfig.typecheck-core.jsonis an explicit 27-entryfilesallowlist with"include": [], andsrc/app/api/v1/models/route.tsis not in it. Adding it is not aone-liner — it pulls in
catalog.ts, which currently emits ~60 pre-existingstrictNullCheckserrors — so I left the tsconfig alone; it deserves its own issue.Why
setTimeout(…, 0)is not equivalentThe builder is overwhelmingly synchronous under the single-threaded App Router. A
macrotask deferral yields the tick but does not wait for the response to be flushed,
so the rebuild pins the event loop while the "served immediately" stale body is still
sitting in the socket — the caller waits out the whole rebuild it was supposed to be
spared. That is precisely the ~50 s-per-call symptom the
CATALOG_CACHE_TTL_MS_DEFAULTdoc comment already describes.
after()runs the task only once the response has beenflushed, which is the #8728 guarantee.
The fix
Both halves of the issue's option list, because the tests require both:
catalogCache.tsimportsafterand schedules throughdefaultBackgroundRefreshScheduler.after()throws outside a Next request scope(the CLI/Electron server, unit tests), so that case falls back to the macrotask —
those callers have no response being flushed, so the deferral is all they ever needed.
resolveCachedCatalogResponsetakes the scheduler as a per-call option, plus aper-call stale-window override the integration test uses to hold an entry in the stale
branch while it measures when the rebuild starts.
getUnifiedModelsResponsetakes the options object again and forwards it, so theroute's
after()wiring is live rather than dead.This does not resurrect #9199
production-build-module-integrity.test.tsguards that the module-level SWR policyaccessor trio stays removed, because an unbounded window could pin an old catalog
forever. The new knob is a per-call parameter: no setter, no module state, and no
production caller passes it —
catalog.tsforwards onlyscheduleBackgroundRefresh.The 30 s bound still holds for every real request. That guard test passes unchanged.
Tests
Both target tests pass without being edited, as the issue requires:
The second test binds a unix-domain socket, which Windows refuses with
EACCES(the test's own sandbox guard only skips on
EPERM), so I could not run it as-is onthis host. I re-ran it verbatim over a TCP loopback port instead — same server, same
assertions, only
listen()changed — and it is red/green on this change:Worth a follow-up: widening that test's sandbox guard from
EPERMto also coverEACCESwould let it skip cleanly on Windows instead of failing, which matters for thenightly Windows compat runs. I did not touch the file here since the issue asks for it
to stay unmodified.
Regression suites, all green:
(
model-catalog-cache-swr-8728reports one extra failure on this Windows host, in itstest.afterrmSyncof the tmpDATA_DIR—EPERMon the still-open SQLite file.Identical on the unmodified base branch; unrelated to this change.)
Commands run
Changed test files: none — this PR turns two existing red tests green.