{"record":{"id":"a6d8c0d4c6e02fd6","repo":"janhq/jan","slug":"failed-to-fetch-models-from-provider-provider-a6d8c0","errorCode":null,"errorMessage":"Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}","messagePattern":"Failed to fetch models from (.+?): (.+?) (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/services/providers/tauri.ts","lineNumber":247,"sourceCode":"        if (Array.isArray(data)) {\n          return data\n            .filter(Boolean)\n            .map((model) =>\n              typeof model === 'object' && 'id' in model ? model.id : model\n            )\n        }\n        if (data.models && Array.isArray(data.models)) {\n          return data.models\n            .map((model: string | { id: string }) =>\n              typeof model === 'string' ? model : model.id\n            )\n            .filter(Boolean)\n        }\n        console.warn('Unexpected response format from provider API:', data)\n        return []\n      }\n\n      throw new Error(\n        `Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`\n      )\n    } catch (error) {\n      console.error('Error fetching models from provider:', error)\n\n      // Preserve structured error messages thrown above\n      const structuredErrorPrefixes = [\n        'Authentication failed',\n        'Access forbidden',\n        'Models endpoint not found',\n        'Failed to fetch models from',\n      ]\n\n      if (\n        error instanceof Error &&\n        structuredErrorPrefixes.some((prefix) =>\n          (error as Error).message.startsWith(prefix)\n        )","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/web-app/src/services/providers/tauri.ts#L229-L265","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If this message appears, audit the retry loop's continue/break/return conditions - a path now exits without returning or throwing.","Inspect lastStatus and lastStatusText embedded in the message to see what the final response actually was.","Add a unit test over the full status x key-index matrix asserting the final attempt always returns or throws."],"exampleFix":"// before: silent safety net that obscures the cause\nthrow new Error(\n  `Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`\n)\n// after: make an unreachable hit self-explanatory\nthrow new Error(\n  `getModels('${provider.provider}') exited its retry loop without a definitive result `\n  + `(last ${lastStatus} ${lastStatusText}, ${keyAttempts.length} key attempts). `\n  + `This is a logic bug in the key-chain iteration.`\n)","handlingStrategy":"try-catch","validationCode":"// Confirm at least one definitive outcome is possible before calling\nconst keyChain = providerRemoteApiKeyChain(provider)\nconst keyAttempts = keyChain.length > 0 ? keyChain : [undefined]\nif (keyAttempts.length === 0) throw new Error('No API key candidates to try')","typeGuard":"function loopCanTerminateDefinitively(keyAttemptsLength: number): boolean {\n  return keyAttemptsLength >= 1\n}","tryCatchPattern":"try {\n  await provider.getModels(p)\n} catch (e) {\n  // This line is effectively unreachable; if seen, treat as an internal bug\n  if (e instanceof Error && /exhausted all.*key attempts/i.test(e.message)) {\n    reportInternalBug(e)\n  } else throw e\n}","preventionTips":["Treat this error as a code-bug signal, not a runtime provider condition.","Keep the retry-loop invariant documented: the final attempt must return or throw.","Cover the loop with tests over all status x key-index combinations."],"tags":["dead-code","retry-loop","provider-api","models","defensive"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}