{"record":{"id":"ce95efabbbd9a93c","repo":"Hmbown/CodeWhale","slug":"the-provider-catalog-changed-restart-loading-its-models","errorCode":null,"errorMessage":"The provider catalog changed; restart loading its models.","messagePattern":"The provider catalog changed; restart loading its models\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/runtime_web/app.mjs","lineNumber":677,"sourceCode":"  let cursor = \"\";\n  for (let page = 0; page < MAX_PROVIDER_MODEL_PAGES; page += 1) {\n    const query = new URLSearchParams({ limit: String(PROVIDER_MODELS_PAGE_SIZE) });\n    if (cursor) query.set(\"cursor\", cursor);\n    const response = await fetchPage(\n      `/v1/providers/${encodeURIComponent(provider)}/models?${query.toString()}`,\n    );\n    if (String(response?.provider || \"\") !== provider) {\n      throw new Error(\"The Runtime returned a model page for a different provider.\");\n    }\n    if (!Array.isArray(response?.models)\n      || response.models.length > PROVIDER_MODELS_PAGE_SIZE\n      || !Number.isSafeInteger(response.total)\n      || response.total < 0\n      || response.total > MAX_PROVIDER_MODELS) {\n      throw new Error(\"The Runtime returned an invalid provider model page.\");\n    }\n    if (expectedTotal !== undefined && expectedTotal !== response.total) {\n      throw new Error(\"The provider catalog changed; restart loading its models.\");\n    }\n    expectedTotal = response.total;\n    const pageEntries = response.models;\n    if (entries.length + pageEntries.length > MAX_PROVIDER_MODELS) {\n      throw new Error(`The provider catalog exceeds ${MAX_PROVIDER_MODELS} models.`);\n    }\n    entries.push(...pageEntries);\n\n    const nextCursor = typeof response?.nextCursor === \"string\"\n      ? response.nextCursor.trim()\n      : \"\";\n    if (!nextCursor) {\n      if (entries.length !== expectedTotal) {\n        throw new Error(\"The Runtime returned an incomplete provider model catalog.\");\n      }\n      return entries;\n    }\n    if (pageEntries.length === 0 || seenCursors.has(nextCursor)) {","sourceCodeStart":659,"sourceCodeEnd":695,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_web/app.mjs#L659-L695","documentation":"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.","triggerScenarios":"Calling collectProviderModelPages while the provider's model list changes server-side between page fetches, so page 2's `total` differs from page 1's.","commonSituations":"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.","solutions":["Close and reopen the new-thread dialog (or reload) to restart pagination with a fresh expectedTotal","Retry after the provider catalog finishes syncing","Ensure a single Runtime instance serves the session (no load balancing across nodes with divergent catalogs)","Avoid changing provider configuration while the model list is loading"],"exampleFix":"// before: swallowing stale totals and appending anyway\nentries.push(...pageEntries);\n// after: restart the load when the catalog changed\nif (expectedTotal !== undefined && page.total !== expectedTotal) {\n  await openNewThreadDialog(); // re-triggers collectProviderModelPages from scratch\n  return;\n}","handlingStrategy":"retry","validationCode":"if (expectedTotal !== undefined && page.total !== expectedTotal) scheduleReload();","typeGuard":"const totalsAgree = (page, expected) => expected === undefined || page?.total === expected;","tryCatchPattern":"try { await loadModels(provider); } catch (e) { if (String(e.message).includes('catalog changed')) await reloadDialog(); }","preventionTips":["Avoid mutating provider configuration while the dialog is open","Serve the session from a single Runtime instance","Retry loads after provider sync jobs finish","Treat this error as restartable, not fatal"],"tags":["pagination","race-condition","provider-catalog"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}