janhq/jan · warning

Conversational extension not available yet

Error message

Conversational extension not available yet

What it means

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.

Solutions

  1. Retry fetchThreads after a short delay until the extension registers (the error is designed to be retried).
  2. Gate the initial fetchThreads call on an 'extensions ready' event from the ExtensionManager.
  3. Check that the conversational/llamacpp extension actually initialized (look at backend/router logs).
  4. If persistent, restart the app so extension registration completes.

Example fix

// before: fetch immediately on mount
useEffect(() => { fetchThreads() }, [])
// after: wait for extension readiness, then fetch with retry
await extensionsReady; try { await fetchThreads() } catch { setTimeout(fetchThreads, 1000) }
Defensive patterns

Strategy: retry

Validate before calling

if (!ExtensionManager.getInstance().get(ExtensionTypeEnum.Conversational)) {
  await waitForExtensionReady(ExtensionTypeEnum.Conversational, { timeoutMs: 10000 })
}

Type guard

function hasConversationalExt(): boolean {
  return !!ExtensionManager.getInstance().get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
}

Try / catch

try {
  threads = await fetchThreads()
} catch (e) {
  if (e instanceof Error && e.message === 'Conversational extension not available yet') {
    retryWithBackoff(fetchThreads) // do NOT treat as empty thread list
  }
}

Prevention

When it happens

Trigger: ExtensionManager.getInstance().get<ConversationalExtension>(ExtensionTypeEnum.Conversational) returns undefined at call time; typically when fetchThreads runs before extensions finish registering after app start or reload.

Common situations: 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.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/3fd26f99de1bc6f8. Report an issue: GitHub.

Appendix: source

Thrown at web-app/src/services/threads/default.ts:33

  assistantModel: { id: string; engine?: string } | undefined,
  fallback?: Thread['model']
): Thread['model'] | undefined {
  if (assistantModel) {
    return { id: assistantModel.id, provider: assistantModel.engine ?? 'llamacpp' }
  }
  return fallback
}

export class DefaultThreadsService implements ThreadsService {
  async fetchThreads(): Promise<Thread[]> {
    const ext = ExtensionManager.getInstance().get<ConversationalExtension>(
      ExtensionTypeEnum.Conversational
    )
    // The extension may not be registered yet during a startup race (e.g. a
    // reload while the llamacpp router is busy). Throw so the caller can retry
    // instead of treating "not ready" as "no threads" and wiping the list.
    if (!ext) {
      throw new Error('Conversational extension not available yet')
    }

    // Let listThreads failures propagate: a rejected invoke means "backend not
    // ready / errored", not "no threads" — the caller retries instead of
    // wiping the list with [].
    const threads = await ext.listThreads()
    if (!Array.isArray(threads)) return []

    // new String("id") !== "id"
    threads.forEach((e) => {
      e.id = e.id?.toString()
      e.assistants?.forEach((a) => {
        a.id = a.id?.toString()
        if (a.model) a.model.id = a.model.id?.toString()
      })
    })

    // Filter out temporary threads from the list

View on GitHub (pinned to 7205d770c1)