#31831·open-webui

issue: Model picker still shows a model as loaded right after Eject

Author: Xain3Created Oct 1, 2026Updated Oct 1, 2026
Labelsbugconfirmed issue

Before Submitting

  • I searched open and closed issues and discussions for an existing report.
  • I reproduced this bug on the latest release AND on the current dev branch, right before submitting this report. I did not just check an old version or rely on a check from days ago.
  • I understand that maintainers want a well-written issue before any code pull request.
  • This is not a security vulnerability.

Installation Method

Git Clone

Open WebUI Version

v0.11.4: 8bd8b4fac5e059578ac0c74b3c18d11139f88b7d ; dev: 015dbc8619568ac077ec1d782661836a63691ba1

Operating System

Debian GNU/Linux 13.6; CPU only, no GPU; Python 3.12.14.

Browser

Chromium 151.0.7922.173, with Playwright 1.63.0

Ollama Version

0.34.4, local loopback instance.

Summary

Follow-up to https://github.com/open-webui/open-webui/discussions/25762.

With Cache Base Model List disabled, idle Eject successfully empties Ollama, but the immediate WebUI model-list refresh can still report loaded:true. The picker then retains the green loaded indicator and Eject button after the provider cache expires, until another refresh.

This is separate from active inference deferring unloading or automatic follow-up generation loading a model again. The primary reproduction below has no inference or background task running.

Related lifecycle report: the companion report “Eject reports completed unloading during inference and automatic follow-ups reload the model”

Expected Behavior

After idle Eject completes and Ollama no longer contains the model, the model-list response and picker should show unloaded state. Post-Eject refresh should obtain fresh runtime metadata rather than the pre-Eject provider-cache entry. The indicator and Eject control should also update when subsequent inference loads a model again.

Actual Behavior

The unload request succeeds and Ollama becomes empty, while the immediate /api/models response still says loaded:true. The green dot and Eject control remain visible for more than 3.5 seconds, despite the provider's one-second cache lifetime. Reopening the picker clears them because it obtains a fresh model list.

A separate ordering-controlled follow-up case also demonstrated stale state in the reverse direction: after the picker refreshed to unloaded state, background inference loaded the model again, but the open picker continued showing no Eject control. A fresh backend list reported loaded:true; page reload restored the control. This controlled case demonstrates the state transition, not its natural frequency.

Steps to Reproduce

Setup, repeated for each tested commit

  1. Use Ollama 0.34.4 listening on 127.0.0.1:11434 and pull the real model:

    bash
    ollama pull qwen3.5:0.8b
  2. Clone Open WebUI, check out one of the exact commits above, and install its slim Python requirements and frontend dependencies in an isolated environment:

    bash
    git clone https://github.com/open-webui/open-webui.git owui-unload-repro
    cd owui-unload-repro
    git checkout 8bd8b4fac5e059578ac0c74b3c18d11139f88b7d
    python3.12 -m venv .venv
    .venv/bin/pip install -r backend/requirements-slim.txt
    npm install

    For the second run, use 015dbc8619568ac077ec1d782661836a63691ba1 and a separate fresh data directory. No application source changes are needed.

  3. Start the backend from backend/ with the following relevant environment. The test used port 18080 for release and 18081 for dev:

    bash
    mkdir -p /tmp/owui-unload-repro-data
    cd backend
    DATA_DIR=/tmp/owui-unload-repro-data \
    USE_SLIM_DOCKER=true OFFLINE_MODE=true \
    WEBUI_SECRET_KEY=local-unload-repro \
    ENABLE_OPENAI_API=false ENABLE_BASE_MODELS_CACHE=false \
    OLLAMA_BASE_URL=http://127.0.0.1:11434 \
    ../.venv/bin/python -m uvicorn open_webui.main:app --host 127.0.0.1 --port 18080
  4. From the repository root in another terminal, run the unchanged frontend:

    bash
    WEBUI_BACKEND_URL=http://127.0.0.1:18080 \
    node node_modules/vite/bin/vite.js --host 127.0.0.1 --port 15173

    Dev used backend port 18081 and frontend port 15174. Open http://127.0.0.1:15173 (or 15174), create the first local admin and sign in. Configure the local Ollama connection if needed. Verify Cache Base Model List is disabled in the saved configuration, not just the startup environment. Do not configure a separate task model for the primary reproduction.

  5. Disable automatic follow-up generation; disable automatic titles, automatic tags, tools, and thinking. Leave the default follow-up prompt unchanged. Use context 2048 and maximum 512 predicted tokens for chat generation; background tasks remain disabled. The precise configuration used is in the next section. Warm the model so the loaded indicator and Eject control are available:

    bash
    curl -sS http://127.0.0.1:11434/api/generate \
      -H 'Content-Type: application/json' \
      -d '{"model":"qwen3.5:0.8b","prompt":"Say hello.","stream":false,"think":false,"keep_alive":"10m","options":{"num_predict":8,"num_ctx":2048}}'

Idle reproduction: no generation or background task in flight

  1. Select qwen3.5:0.8b, open the chat model picker, hover the loaded model row, and verify that the green dot and Eject model button are visible.
  2. Prime the separate provider cache with authenticated WebUI GET /api/models, then immediately click Eject within one second. The browser harness deliberately primed this cache after the picker was ready, to make the race reproducible. The provider responses were real, not mocked.
  3. Observe HTTP 200 {"status":true} and the success toast. Query http://127.0.0.1:11434/api/ps: it is already empty. The frontend's immediate /api/models refresh still reports the target model as loaded:true.
  4. Keep the picker open without refreshing it. After at least 3.5 seconds the green dot and Eject control are still visible. Reopen the picker: the fresh list reports loaded:false, and both controls disappear.

For a deterministic HTTP-only check in the logged-in browser's developer console, after warming the model and allowing any earlier cache entry to expire:

javascript
const api = async (path, options = {}) => {
  const response = await fetch(path, {
    ...options,
    headers: {Authorization: `Bearer ${localStorage.token}`, 'Content-Type': 'application/json'}
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
};
const state = data => data.data.find(model => model.id === 'qwen3.5:0.8b')?.loaded;
await api('/api/models'); // Prime the provider cache.
console.log(await api('/api/models/unload', {
  method: 'POST', body: JSON.stringify({model: 'qwen3.5:0.8b'})
}));
console.log('immediate loaded:', state(await api('/api/models')));
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('after cache expiry:', state(await api('/api/models')));

Compare the immediate result with a concurrent Ollama /api/ps check. The script prints model state and the unload response; it does not print the session token. The rendered-button reproduction above additionally checks whether the picker refreshes after cache expiry.

Logs, Screenshots, and Config

Three idle HTTP runs per version; all real provider traffic

POST /api/models/unload
request:  {"model":"qwen3.5:0.8b"}
response: HTTP 200 {"status":true}

first post-response Ollama GET /api/ps: {"models":[]}
immediate WebUI GET /api/models: target model loaded=true
WebUI GET /api/models after 3 seconds: target model loaded=false

This combination occurred in 3/3 idle runs on each version. The first Ollama samples were empty 26.8–34.8 ms after the unload response. Unload responses took 8.07–12.06 ms on release and 10.89–13.74 ms on dev. These are sampled timings, not exact internal shutdown timestamps.

One full rendered idle-browser case per version also confirmed the success toast, empty Ollama, retained green dot/Eject control beyond 3.5 seconds, and cleared state on reopening. Both recorded no page exception.

Machine-readable evidence

idle-stale-state-evidence.json contains the six accepted idle HTTP cases (three per version) and two accepted idle browser cases (one per version). It records the exact commits, saved cache configuration, HTTP results, sampled Ollama presence, WebUI loaded state, and rendered-state observations. In particular, it corroborates the screenshots by showing that Ollama was already empty while the immediate WebUI response still reported loaded:true; a later fresh list reported loaded:false. Timings are sampled observations, not exact internal shutdown timestamps. Active generation and injected-failure cases are excluded.

idle-stale-state-evidence.json

Screenshots

The paired screenshots below show the rendered state immediately after idle Eject and after reopening the picker. They illustrate the UI symptom; actual Ollama absence and persistence beyond 3.5 seconds are established by the JSON observations above. Optional Cache Base Model List was disabled in both versions.

Image

Figure 1. v0.11.4 / main, immediately after idle Eject: “Model unloaded successfully” appears, but the green loaded indicator and Eject control remain visible. The corresponding JSON confirms that Ollama was already empty.

Image

Figure 2. v0.11.4 / main, after reopening the model picker: a fresh model-list response reports loaded:false, and the green indicator and Eject control are gone.

Image

Figure 3. Current upstream dev, immediately after idle Eject: the success toast appears while the green loaded indicator and Eject control remain visible. The corresponding JSON confirms the same mismatch with empty Ollama runtime.

Image

Figure 4. Current upstream dev, after reopening the model picker: refreshing the model list clears the green indicator and Eject control, matching the unloaded runtime state.

Configuration

  • Optional Cache Base Model List disabled, saved through the configuration API and verified by readback; ENABLE_BASE_MODELS_CACHE=false at startup.
  • Independent provider cache at its current default MODELS_CACHE_TTL=1. The historical 300-second value discussed in #25762 was not used here.
  • Local Ollama connection with no prefix, custom authentication, filters, or actions; native qwen3.5:0.8b id, no workspace alias.
  • User settings: title.auto:false, autoTags:false, autoFollowUps:false; chat parameters num_predict:512, num_ctx:2048, think:false, function_calling:"legacy". No inference was in flight during idle Eject.
  • Unchanged source, separate fresh SQLite databases, slim Python dependencies, local fixture admins. Exact launch environment and ports are in Steps to Reproduce.

No Docker logs apply to this Git Clone installation. Relevant request/result evidence is included above. Raw model-download logs containing signed storage URLs are omitted.

Additional Information

  • Originating discussion: https://github.com/open-webui/open-webui/discussions/25762.
  • Closed #29013 describes optional base-model caching and missing workspace-model metadata. This report uses a native model id with optional caching disabled, and isolates the separate provider cache plus the post-Eject picker refresh.
  • Closed #28764 concerns a model object sent instead of its id, causing HTTP 422. The requests here send the string id and return HTTP 200.

Source pointers on the tested dev commit

The observations and source support a provider-cache race followed by missing fresh picker state. They do not show a failed idle unload. The active-inference/follow-up lifecycle behavior is tracked separately in the companion report “Eject reports completed unloading during inference and automatic follow-ups reload the model” (link to be added after filing). No code PR is being proposed.](https://github.com/open-webui/open-webui/issues/31832)