{"record":{"id":"3fd26f99de1bc6f8","repo":"janhq/jan","slug":"conversational-extension-not-available-yet","errorCode":null,"errorMessage":"Conversational extension not available yet","messagePattern":"Conversational extension not available yet","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"web-app/src/services/threads/default.ts","lineNumber":33,"sourceCode":"  assistantModel: { id: string; engine?: string } | undefined,\n  fallback?: Thread['model']\n): Thread['model'] | undefined {\n  if (assistantModel) {\n    return { id: assistantModel.id, provider: assistantModel.engine ?? 'llamacpp' }\n  }\n  return fallback\n}\n\nexport class DefaultThreadsService implements ThreadsService {\n  async fetchThreads(): Promise<Thread[]> {\n    const ext = ExtensionManager.getInstance().get<ConversationalExtension>(\n      ExtensionTypeEnum.Conversational\n    )\n    // The extension may not be registered yet during a startup race (e.g. a\n    // reload while the llamacpp router is busy). Throw so the caller can retry\n    // instead of treating \"not ready\" as \"no threads\" and wiping the list.\n    if (!ext) {\n      throw new Error('Conversational extension not available yet')\n    }\n\n    // Let listThreads failures propagate: a rejected invoke means \"backend not\n    // ready / errored\", not \"no threads\" — the caller retries instead of\n    // wiping the list with [].\n    const threads = await ext.listThreads()\n    if (!Array.isArray(threads)) return []\n\n    // new String(\"id\") !== \"id\"\n    threads.forEach((e) => {\n      e.id = e.id?.toString()\n      e.assistants?.forEach((a) => {\n        a.id = a.id?.toString()\n        if (a.model) a.model.id = a.model.id?.toString()\n      })\n    })\n\n    // Filter out temporary threads from the list","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/services/threads/default.ts#L15-L51","documentation":"DefaultThreadsService.fetchThreads throws this when the ConversationalExtension is not yet registered in the ExtensionManager. It is deliberately thrown (instead of returning []) so callers can retry during a startup race — e.g. a reload while the llamacpp router is busy — rather than wiping the thread list believing there are no threads.","triggerScenarios":"ExtensionManager.getInstance().get<ConversationalExtension>(ExtensionTypeEnum.Conversational) returns undefined at call time; typically when fetchThreads runs before extensions finish registering after app start or reload.","commonSituations":"App reload triggered while the llamacpp backend/router is still initializing; hot-reload in dev re-mounting the thread list before the extension manager is ready; slow machine where extension registration lags the UI.","solutions":["Retry fetchThreads after a short delay until the extension registers (the error is designed to be retried).","Gate the initial fetchThreads call on an 'extensions ready' event from the ExtensionManager.","Check that the conversational/llamacpp extension actually initialized (look at backend/router logs).","If persistent, restart the app so extension registration completes."],"exampleFix":"// before: fetch immediately on mount\nuseEffect(() => { fetchThreads() }, [])\n// after: wait for extension readiness, then fetch with retry\nawait extensionsReady; try { await fetchThreads() } catch { setTimeout(fetchThreads, 1000) }","handlingStrategy":"retry","validationCode":"if (!ExtensionManager.getInstance().get(ExtensionTypeEnum.Conversational)) {\n  await waitForExtensionReady(ExtensionTypeEnum.Conversational, { timeoutMs: 10000 })\n}","typeGuard":"function hasConversationalExt(): boolean {\n  return !!ExtensionManager.getInstance().get<ConversationalExtension>(ExtensionTypeEnum.Conversational)\n}","tryCatchPattern":"try {\n  threads = await fetchThreads()\n} catch (e) {\n  if (e instanceof Error && e.message === 'Conversational extension not available yet') {\n    retryWithBackoff(fetchThreads) // do NOT treat as empty thread list\n  }\n}","preventionTips":["Await the extension-manager 'ready' signal before the first threads fetch.","Treat this error as retryable, never as an empty result.","On reload, re-initialize extensions before remounting thread-list UI."],"tags":["race-condition","startup","extension","retry"],"backgroundTag":"missing-dependency","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}