janhq/jan · error · Error

Unexpected error while fetching models from ${provider.provi

Error message

Unexpected error while fetching models from ${provider.provider}: ${error instanceof Error ? error.message : 'Unknown error'}

What it means

The catch-all fallback in getModels for any error that is neither a structured provider error (162) nor a connection-style failure (163). Typically wraps JSON parse failures, unexpected thrown types, or runtime errors inside the data-shaping branches. It preserves the original message so it isn't lost.

Source

Thrown at web-app/src/services/providers/tauri.ts:278

      if (
        error instanceof Error &&
        structuredErrorPrefixes.some((prefix) =>
          (error as Error).message.startsWith(prefix)
        )
      ) {
        throw new Error(error.message)
      }

      // Provide helpful error message for any connection errors
      if (error instanceof Error && error.message.includes('fetch')) {
        throw new Error(
          `Cannot connect to ${provider.provider} at ${provider.base_url}. Please check that the service is running and accessible.`
        )
      }

      // Generic fallback
      throw new Error(
        `Unexpected error while fetching models from ${provider.provider}: ${error instanceof Error ? error.message : 'Unknown error'}`
      )
    }
  }

  async updateSettings(
    providerName: string,
    settings: ProviderSetting[]
  ): Promise<void> {
    try {
      // API keys are persisted to the OS keyring only (via
      // register_provider_config), never to the extension's settings.json.
      // Blank the key entries at this single chokepoint regardless of caller.
      const isSecretKey = (key: string) =>
        key === 'api-key' || key === API_KEY_FALLBACKS_SETTING_KEY
      return ExtensionManager.getInstance()
        .getEngine(providerName)
        ?.updateSettings(

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Inspect the embedded original error message - it pinpoints the real failure (e.g., 'Unexpected token < in JSON').
  2. Reproduce with curl to inspect the raw body and Content-Type of /models.
  3. If JSON parsing fails, the provider may be returning HTML with a 200 - verify the endpoint and Content-Type.
  4. Add a Content-Type check before calling response.json().

Example fix

// before: 200 OK with HTML body -> response.json() throws -> generic fallback
const data = await response.json()
// after: guard Content-Type before parsing
const ct = response.headers.get('content-type') ?? ''
if (!ct.includes('application/json')) {
  throw new Error(`Expected JSON from ${provider.provider}/models but got '${ct}'`)
}
const data = await response.json()
Defensive patterns

Strategy: try-catch

Validate before calling

async function safeJson(res: Response): Promise<unknown> {
  const ct = res.headers.get('content-type') ?? ''
  if (!ct.includes('application/json')) throw new Error(`Non-JSON response: ${ct}`)
  return res.json()
}

Type guard

function isJsonContentType(res: Response): boolean {
  return (res.headers.get('content-type') ?? '').includes('application/json')
}

Try / catch

try {
  await provider.getModels(p)
} catch (e) {
  // Anything not matching structured/connection prefixes lands here
  logUnexpected(e)
  showGenericError()
}

Prevention

When it happens

Trigger: response.json() throws on an invalid/non-JSON body; an exception inside the .map/.filter shaping branches; a non-Error value thrown somewhere in the try block; the provider returns 200 OK with an HTML error page.

Common situations: Provider returns 200 with a non-JSON body (HTML error page, plain text); provider returns an unexpected data shape that breaks the mapping logic; a regression in the parsing branches throws a TypeError.

Related errors


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