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
- 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).
- Check runtime/daemon health and logs: a job stuck in 'running' usually means the daemon worker hung; restart the runtime daemon and retry.
- 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).
- 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
- Keep runtimes warm (start the daemon before opening the model picker) so discovery is not cold.
- If your fleet has slow runtimes, raise POLL_TIMEOUT_MS to match measured discovery time.
- Surface the timed-out state in the UI with a retry affordance and manual model entry, matching the intended picker behavior.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- model discovery failed (status: ${current.status})
- creation_studio.builder.start_failed
- timed out waiting for workspace creation
- creation_studio.create_failed
- Team issue scope is not supported by the Table query
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/f07987d20df39a49.
Report an issue: GitHub.