janhq/jan · warning · Error

Conversational extension not available yet

Error message

Conversational extension not available yet

What it means

Thrown by DefaultThreadsService.fetchThreads when ExtensionManager.get<ConversationalExtension>(ExtensionTypeEnum.Conversational) returns undefined. By design it signals a transient startup race (e.g., a reload while the llamacpp router is still initializing) so the caller retries instead of treating 'not ready' as 'no threads' and wiping the cached list.

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 fad3f12a14)

Solutions

  1. Retry fetchThreads after a short backoff - the extension usually registers within a few hundred ms of startup.
  2. Check the extension manager logs to confirm the Conversational extension loaded successfully.
  3. If it persists, verify the conversational extension is packaged and enabled for this build.

Example fix

// before: a single call on a cold start wipes the thread list
const threads = await threadsService.fetchThreads()
// after: retry specifically on the 'not available yet' signal
async function loadThreads(maxAttempts = 5) {
  for (let i = 0; i < maxAttempts; i++) {
    try { return await threadsService.fetchThreads() }
    catch (e) {
      if (e instanceof Error && e.message.includes('not available yet') && i < maxAttempts - 1) {
        await new Promise(r => setTimeout(r, 300 * (i + 1))); continue
      }
      throw e
    }
  }
}
Defensive patterns

Strategy: retry

Validate before calling

function isConversationalReady(): boolean {
  return !!ExtensionManager.getInstance().get(ExtensionTypeEnum.Conversational)
}

Type guard

function isExtensionNotReadyError(e: unknown): boolean {
  return e instanceof Error && e.message.includes('not available yet')
}

Try / catch

for (let i = 0; i < 5; i++) {
  try { setThreads(await threadsService.fetchThreads()); break }
  catch (e) {
    if (isExtensionNotReadyError(e) && i < 4) {
      await new Promise(r => setTimeout(r, 300 * (i + 1))); continue
    }
    throw e
  }
}

Prevention

When it happens

Trigger: The Conversational extension is not registered yet at the moment of the call - get() returns undefined. Happens during cold start, during a reload while another router is busy, or when the extension failed/was disabled.

Common situations: App cold start before extensions finish registering; reload during model loading; the conversational extension crashed on init; a build that excluded the conversational extension.

Related errors


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