multica-ai/multica · error

model discovery timed out

Error message

model discovery timed out

What it means

Thrown by resolveRuntimeModels in packages/core/runtimes/models.ts when the async model-discovery job on the runtime did not reach a terminal status within POLL_TIMEOUT_MS (30s, checked every 500ms). The function initiates a list-models job via api.initiateListModels and polls api.getListModelsResult until the status leaves 'pending'/'running'. It is a client-side deadline, not a server error: the daemon may still be working (large model catalog, slow runtime startup) when the client gives up.

Source

Thrown at packages/core/runtimes/models.ts:45

// the request times out. Returns both the models list and a
// `supported` flag: `supported=false` means the provider ignores
// per-agent model selection entirely (hermes today) — the UI uses
// this to disable its dropdown instead of accepting a value that
// wouldn't be honoured at runtime.
//
// `cached` reports that the server answered from its catalog cache rather than
// a live daemon round trip (MUL-5444). It is not cosmetic: it feeds the
// staleTime policy below so the client never extends the server's staleness
// window past what the server itself promises.
export async function resolveRuntimeModels(
  runtimeId: string,
): Promise<RuntimeModelsResult> {
  const initial = await api.initiateListModels(runtimeId);
  const start = Date.now();
  let current = initial;
  while (current.status === "pending" || current.status === "running") {
    if (Date.now() - start > POLL_TIMEOUT_MS) {
      throw new Error("model discovery timed out");
    }
    await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
    current = await api.getListModelsResult(runtimeId, initial.id);
  }
  // Only an explicit `completed` is a catalog. Anything else — failed, timeout,
  // or a status this client does not know (newer server, or a response that fell
  // back to the malformed-record shape) — is surfaced as an error so the picker
  // shows "discovery failed" and keeps manual entry available. Treating an
  // unrecognised status as success would render an empty dropdown that looks
  // authoritative.
  if (current.status !== "completed") {
    throw new Error(
      current.error || `model discovery failed (status: ${current.status})`,
    );
  }
  return {
    models: current.models ?? [],
    supported: current.supported !== false,

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Retry the call — a second invocation often succeeds because the daemon-side discovery continues and later answers from its catalog cache (the 'cached' flag path).
  2. Check runtime/daemon health and logs: a job stuck in 'running' usually means the daemon worker hung; restart the runtime daemon and retry.
  3. If discovery legitimately takes >30s in your environment, raise POLL_TIMEOUT_MS in packages/core/runtimes/models.ts (it is a module constant next to the poll loop).
  4. If it always times out, verify the runtime is reachable at all — use the runtime status API before invoking the picker.

Example fix

// before
const POLL_TIMEOUT_MS = 30_000;

// after (only if slow runtimes are expected)
const POLL_TIMEOUT_MS = 90_000;
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check runtime health before discovery
const status = await api.getRuntime(runtimeId);
if (status.state !== "online") {
  throw new Error(`runtime ${runtimeId} is ${status.state}; not ready for discovery`);
}

Type guard

function isRuntimeOnline(s: { state: string } | undefined): boolean {
  return s?.state === "online";
}

Try / catch

try {
  const result = await resolveRuntimeModels(runtimeId);
} catch (err) {
  if (err instanceof Error && err.message === "model discovery timed out") {
    // daemon may finish server-side; one retry often hits the catalog cache
    return resolveRuntimeModels(runtimeId);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling resolveRuntimeModels(runtimeId) against a runtime whose daemon takes >30s to enumerate models (cold Ollama start, large model directory on slow disk, network-attached model store); or a runtime that reports status 'pending'/'running' indefinitely because the daemon job hung; or getListModelsResult repeatedly returning a still-running result.

Common situations: First model discovery after installing a runtime with many models; runtime hosted on a remote/slow machine; daemon stuck mid-discovery after a partial crash; CI environments with slow disk I/O.

Understand the failure class

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/f07987d20df39a49. Report an issue: GitHub.