Hmbown/CodeWhale · warning

The provider catalog changed; restart loading its models.

Error message

The provider catalog changed; restart loading its models.

What it means

Thrown when a subsequent page of the provider catalog reports a different `total` than earlier pages. The loader captures the total from the first page and requires it to stay constant across all pages; a drift means the catalog mutated mid-pagination and the accumulated entries would be inconsistent.

Solutions

  1. Close and reopen the new-thread dialog (or reload) to restart pagination with a fresh expectedTotal
  2. Retry after the provider catalog finishes syncing
  3. Ensure a single Runtime instance serves the session (no load balancing across nodes with divergent catalogs)
  4. Avoid changing provider configuration while the model list is loading

Example fix

// before: swallowing stale totals and appending anyway
entries.push(...pageEntries);
// after: restart the load when the catalog changed
if (expectedTotal !== undefined && page.total !== expectedTotal) {
  await openNewThreadDialog(); // re-triggers collectProviderModelPages from scratch
  return;
}
Defensive patterns

Strategy: retry

Validate before calling

if (expectedTotal !== undefined && page.total !== expectedTotal) scheduleReload();

Type guard

const totalsAgree = (page, expected) => expected === undefined || page?.total === expected;

Try / catch

try { await loadModels(provider); } catch (e) { if (String(e.message).includes('catalog changed')) await reloadDialog(); }

Prevention

When it happens

Trigger: Calling collectProviderModelPages while the provider's model list changes server-side between page fetches, so page 2's `total` differs from page 1's.

Common situations: Provider syncs a model list (models added/removed) while the user has the new-thread dialog open; load balancer routing page requests to different Runtime instances with different catalogs; a hot reload or config change updating the provider config mid-load.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/ce95efabbbd9a93c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_web/app.mjs:677

  let cursor = "";
  for (let page = 0; page < MAX_PROVIDER_MODEL_PAGES; page += 1) {
    const query = new URLSearchParams({ limit: String(PROVIDER_MODELS_PAGE_SIZE) });
    if (cursor) query.set("cursor", cursor);
    const response = await fetchPage(
      `/v1/providers/${encodeURIComponent(provider)}/models?${query.toString()}`,
    );
    if (String(response?.provider || "") !== provider) {
      throw new Error("The Runtime returned a model page for a different provider.");
    }
    if (!Array.isArray(response?.models)
      || response.models.length > PROVIDER_MODELS_PAGE_SIZE
      || !Number.isSafeInteger(response.total)
      || response.total < 0
      || response.total > MAX_PROVIDER_MODELS) {
      throw new Error("The Runtime returned an invalid provider model page.");
    }
    if (expectedTotal !== undefined && expectedTotal !== response.total) {
      throw new Error("The provider catalog changed; restart loading its models.");
    }
    expectedTotal = response.total;
    const pageEntries = response.models;
    if (entries.length + pageEntries.length > MAX_PROVIDER_MODELS) {
      throw new Error(`The provider catalog exceeds ${MAX_PROVIDER_MODELS} models.`);
    }
    entries.push(...pageEntries);

    const nextCursor = typeof response?.nextCursor === "string"
      ? response.nextCursor.trim()
      : "";
    if (!nextCursor) {
      if (entries.length !== expectedTotal) {
        throw new Error("The Runtime returned an incomplete provider model catalog.");
      }
      return entries;
    }
    if (pageEntries.length === 0 || seenCursors.has(nextCursor)) {

View on GitHub (pinned to 73e0f67d83)