janhq/jan · error · Error

Failed to fetch model catalog: ${error instanceof Error ? er

Error message

Failed to fetch model catalog: ${error instanceof Error ? error.message : 'Unknown error'}

What it means

Thrown by the catch block of ModelService.fetchModelCatalog (default.ts:63). It wraps any error from the try block — the inner 'response not ok' throw [153], a network failure (DNS, offline, TLS), or a JSON parse failure — into a uniform 'Failed to fetch model catalog' message with the underlying error.message or 'Unknown error'.

Source

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

  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)

      if (!response.ok) {
        console.error(
          `Failed to fetch latest Jan model: ${response.status} ${response.statusText}`
        )
        return null
      }

      const data = await response.json()

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Distinguish network errors from HTTP-status errors so the inner [153] status is not lost — rethrow known Error instances unchanged.
  2. Verify network connectivity and DNS resolution for the catalog host.
  3. If a proxy intercepts with HTML, whitelist the catalog host.
  4. Cache the last successful catalog locally so a fetch failure degrades gracefully.

Example fix

// before
catch (error) {
  console.error('Error fetching model catalog:', error)
  throw new Error(`Failed to fetch model catalog: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
// after: preserve the structured inner error instead of re-wrapping it
catch (error) {
  console.error('Error fetching model catalog:', error)
  if (error instanceof Error && error.message.startsWith('Failed to fetch model catalog')) throw error
  throw new Error(`Failed to fetch model catalog: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function safeFetchJson(url: string): Promise<ModelCatalog | null> {
  const r = await fetch(url)
  if (!r.ok) return null
  try { return await r.json() as ModelCatalog } catch { return null }
}

Type guard

function isModelCatalog(x: unknown): x is ModelCatalog {
  return typeof x === 'object' && x !== null && Array.isArray((x as ModelCatalog).models)
}

Try / catch

try {
  return await this.fetchModelCatalog()
} catch (error) {
  console.error('catalog unavailable, degrading to cache', error)
  return getCachedCatalog() ?? []
}

Prevention

When it happens

Trigger: fetch() itself rejects (network unreachable, DNS failure, CORS, TLS error), response.json() throws (non-JSON body / empty body), or the inner [153] throw propagates into the catch and gets re-wrapped (note: the inner status detail is lost, replaced by the Error.message).

Common situations: User is offline; DNS for the catalog host fails; a proxy returns an HTML error page that fails JSON parsing; TLS cert expired on the catalog endpoint; the inner [153] error gets double-wrapped, obscuring the original status.

Related errors


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