janhq/jan · error · Error

Failed to fetch models from ${provider.provider}: ${lastStat

Error message

Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}

What it means

Defensive throw at the end of the API-key attempt loop in getModels, reporting the last HTTP status seen across all key attempts. Under the current loop logic it is effectively unreachable: the final key iteration can never hit the `continue` branch (the guard requires ki < keyAttempts.length - 1), so that iteration always either returns data or throws a more specific status error (401/403/404 or error 160). Treat hitting this line as a signal that the retry loop's invariant has been broken by a refactor.

Source

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

        if (Array.isArray(data)) {
          return data
            .filter(Boolean)
            .map((model) =>
              typeof model === 'object' && 'id' in model ? model.id : model
            )
        }
        if (data.models && Array.isArray(data.models)) {
          return data.models
            .map((model: string | { id: string }) =>
              typeof model === 'string' ? model : model.id
            )
            .filter(Boolean)
        }
        console.warn('Unexpected response format from provider API:', data)
        return []
      }

      throw new Error(
        `Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`
      )
    } catch (error) {
      console.error('Error fetching models from provider:', error)

      // Preserve structured error messages thrown above
      const structuredErrorPrefixes = [
        'Authentication failed',
        'Access forbidden',
        'Models endpoint not found',
        'Failed to fetch models from',
      ]

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

View on GitHub (pinned to fad3f12a14)

Solutions

  1. If this message appears, audit the retry loop's continue/break/return conditions - a path now exits without returning or throwing.
  2. Inspect lastStatus and lastStatusText embedded in the message to see what the final response actually was.
  3. Add a unit test over the full status x key-index matrix asserting the final attempt always returns or throws.

Example fix

// before: silent safety net that obscures the cause
throw new Error(
  `Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`
)
// after: make an unreachable hit self-explanatory
throw new Error(
  `getModels('${provider.provider}') exited its retry loop without a definitive result `
  + `(last ${lastStatus} ${lastStatusText}, ${keyAttempts.length} key attempts). `
  + `This is a logic bug in the key-chain iteration.`
)
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm at least one definitive outcome is possible before calling
const keyChain = providerRemoteApiKeyChain(provider)
const keyAttempts = keyChain.length > 0 ? keyChain : [undefined]
if (keyAttempts.length === 0) throw new Error('No API key candidates to try')

Type guard

function loopCanTerminateDefinitively(keyAttemptsLength: number): boolean {
  return keyAttemptsLength >= 1
}

Try / catch

try {
  await provider.getModels(p)
} catch (e) {
  // This line is effectively unreachable; if seen, treat as an internal bug
  if (e instanceof Error && /exhausted all.*key attempts/i.test(e.message)) {
    reportInternalBug(e)
  } else throw e
}

Prevention

When it happens

Trigger: Only reachable if every loop iteration executed `continue`, which requires [401,403,429].includes(status) AND ki < keyAttempts.length - 1. The last iteration can never satisfy the second condition, so in practice this line is dead code. It would fire only if the loop guard or continue/break conditions were changed to allow exiting without a definitive outcome.

Common situations: Should not occur in the current implementation. Would appear after a refactor that adds a non-terminal break, an early loop exit, or changes the key-chain iteration semantics.

Related errors


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