Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/server/src/provider/ModelManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,22 @@ describe("ModelManifest service", () => {
),
);

it.live("keeps a newer bundled catalog when the remote manifest is an older edit", () =>
Effect.gen(function* () {
const service = yield* make;
assert.deepStrictEqual(yield* service.refresh, BUNDLED_MODEL_MANIFEST);
assert.deepStrictEqual(yield* service.current, BUNDLED_MODEL_MANIFEST);
}).pipe(
Effect.scoped,
Effect.provide(
serviceLayers({
prefix: "model-manifest-stale-remote-test",
response: () => Response.json({ ...REMOTE_MANIFEST, updatedAt: "2000-01-01T00:00:00Z" }),
}),
),
),
);

it.effect("preserves the last-good remote cache when later payloads are invalid", () => {
let responseIndex = 0;
const responses = [REMOTE_CLAUDE_MANIFEST, ...INVALID_REMOTE_MANIFESTS];
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/provider/ModelManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*
* Provider catalogs and legacy classification live in `model-manifest.json`.
* The bundled copy ships with every release; at runtime the service refreshes
* it from the same file on `main`. Preference order is remote, then the last
* successful on-disk copy, then the bundle. A failed fetch never fails a
* provider check.
* it from the same file on `main`. The newest edit among remote, on-disk,
* and bundled copies wins. A failed fetch never fails a provider check.
*
* Providers with authoritative discovery can use only the classification
* overlay. Providers with static catalogs can resolve presentation and
Expand Down Expand Up @@ -398,6 +397,7 @@ export const make = Effect.gen(function* () {
Effect.catchCause(() => Effect.succeed(null)),
);
if (fetched === null) return manifest;
if (manifestUpdatedAtMs(fetched) < manifestUpdatedAtMs(manifest)) return manifest;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the normal TTL after a stale successful fetch

When a release's bundled manifest is newer than the copy on main—the exact scenario this branch handles—this early return leaves fetchedAtMs unchanged even though the HTTP request succeeded. Provider health checks repeatedly trigger this refresh, so after the five-minute lastAttemptMs window each active server can download the same known-stale manifest again rather than observing the one-hour successful-fetch TTL, increasing request volume by up to 12× until main catches up. Record the successful probe time, or otherwise TTL-gate stale-but-valid responses, before returning the bundled manifest.

Useful? React with 👍 / 👎.


manifest = fetched;
fetchedAtMs = now;
Expand Down
20 changes: 16 additions & 4 deletions apps/server/src/provider/model-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-09-12T00:41:55Z",
"updatedAt": "2026-09-23T00:00:00Z",
"currentModels": {
"codex": [
"gpt-6-astra",
Expand All @@ -16,7 +16,7 @@
"providers": {
"claudeAgent": {
"defaults": {
"chat": "claude-fable-5-1"
"chat": "claude-opus-5-5"
},
"profiles": {
"fable-5": {
Expand Down Expand Up @@ -577,11 +577,23 @@
}
}
},
{
"slug": "claude-opus-5-5",
"name": "Claude Opus 5.5",
"aliases": ["opus", "opus-5.5", "claude-opus-5.5"],
"status": "current",
"profile": "opus-5",
"adapter": {
"claudeCode": {
"minVersion": "2.1.280"
}
}
},
{
"slug": "claude-opus-5",
"name": "Claude Opus 5",
"aliases": ["opus", "opus-5", "claude-opus-5.0", "claude-opus-5-0"],
"status": "current",
"aliases": ["opus-5", "claude-opus-5.0", "claude-opus-5-0"],
"status": "legacy",
"profile": "opus-5",
"adapter": {
"claudeCode": {
Expand Down
7 changes: 4 additions & 3 deletions docs/internals/model-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ releases. Failed fetches or invalid data preserve the last usable manifest.
Remote data must pass both catalog-reference validation and the owning provider's
adapter validation before replacing the cache.

A newer bundle outranks the cached remote manifest by `updatedAt`, so a release can
correct model data before the next successful fetch. Bump `updatedAt` whenever the
file changes. Fetch time cannot establish which copy contains the newer edit.
A newer bundle outranks cached and fetched remote manifests by `updatedAt`, so a
release can correct model data even while the remote copy lags. Bump `updatedAt`
whenever the file changes. Fetch time cannot establish which copy contains the
newer edit.

Generic catalog data describes presentation and capabilities. Each provider owns
its adapter schema and dispatch mappings. Claude uses the manifest for its entire
Expand Down