janhq/jan · error · Error

Failed to fetch model catalog: ${response.status} ${response

Error message

Failed to fetch model catalog: ${response.status} ${response.statusText}

What it means

Thrown by ModelService.fetchModelCatalog (default.ts:54) when the HTTP response from MODEL_CATALOG_URL is not OK (response.ok is false). The status and statusText are embedded in the message. This is the inner 'response not ok' branch before JSON parsing; the surrounding catch re-wraps it as error [154].

Source

Thrown at web-app/src/services/models/default.ts:54

export class DefaultModelsService implements ModelsService {
  private getEngine(provider: string = defaultProvider) {
    return EngineManager.instance().get(provider) as AIEngine | undefined
  }

  async getModel(modelId: string): Promise<modelInfo | undefined> {
    return this.getEngine()?.get(modelId)
  }

  async fetchModels(): Promise<modelInfo[]> {
    return this.getEngine()?.list() ?? []
  }

  async fetchModelCatalog(): Promise<ModelCatalog> {
    try {
      const response = await fetch(MODEL_CATALOG_URL)

      if (!response.ok) {
        throw new Error(
          `Failed to fetch model catalog: ${response.status} ${response.statusText}`
        )
      }

      const catalog: ModelCatalog = await response.json()
      return catalog
    } catch (error) {
      console.error('Error fetching model catalog:', error)
      throw new Error(
        `Failed to fetch model catalog: ${error instanceof Error ? error.message : 'Unknown error'}`
      )
    }
  }

  async fetchLatestJanModel(): Promise<CatalogModel | null> {
    try {
      const response = await fetch(LATEST_JAN_MODEL_URL)

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Retry — most catalog failures are transient (5xx / CDN).
  2. Update the app so MODEL_CATALOG_URL points at the current catalog host.
  3. Check network/proxy: ensure the catalog host is reachable and not blocked by a captive portal or firewall.
  4. Fall back to the cached catalog if available rather than failing the whole Hub view.

Example fix

// before
if (!response.ok) {
  throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)
}
// after: retry once on transient errors, then degrade to cached catalog
if (!response.ok) {
  if (response.status >= 500 && attempt < 2) { await delay(500); continue }
  return getCachedCatalog() ?? throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)
}
Defensive patterns

Strategy: retry

Validate before calling

async function isCatalogReachable(url: string): Promise<boolean> {
  try {
    const r = await fetch(url, { method: 'HEAD' })
    return r.ok || r.status === 405  // some hosts reject HEAD
  } catch { return false }
}

Type guard

function isOkResponse(r: Response): boolean {
  return r.ok
}

Try / catch

for (let attempt = 0; attempt < 2; attempt++) {
  try {
    const response = await fetch(MODEL_CATALOG_URL)
    if (response.ok) return await response.json()
    if (response.status < 500) throw new Error(`Failed to fetch model catalog: ${response.status} ${response.statusText}`)
    await new Promise(r => setTimeout(r, 500))
  } catch (e) { if (attempt === 1) throw e }
}

Prevention

When it happens

Trigger: The remote model catalog endpoint returns a non-2xx status: 404 (catalog URL moved), 500/502/503 (catalog server down), 429 (rate-limited), or a CDN error.

Common situations: Catalog hosting outage; the hardcoded MODEL_CATALOG_URL changed in a new release but the client is old; corporate firewall returns a blocking page with a non-2xx status; transient CDN blip.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/f532341fc573d365. Report an issue: GitHub.