# "Model not found" Troubleshooting (Open WebUI + llama.cpp/llama-swap)

## Symptom

```
WARNING open_webui.main:chat_completion:2015 - Error processing chat metadata: Model not found
POST /api/chat/completions -> 400
```

User sees a toast error "Model not found" when sending a message. Models may appear in the dropdown but fail when used.

## Diagnostic Flow

### 1. Verify the backend is reachable

```bash
# From the host
curl -s http://127.0.0.1:9292/v1/models | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Found {len(d[\"data\"])} models: {[m[\"id\"] for m in d[\"data\"]]}')"

# From inside the Open WebUI container (to test Docker networking)
docker exec open-webui sh -c 'curl -s --connect-timeout 5 http://host.docker.internal:9292/v1/models' 2>/dev/null | head -c 200
```

If the inside-container call fails with connection timeout but the host call works, the issue is Docker bridge/UFW routing (see `references/docker-gateway-ufw.md`).

### 2. Check what models are registered in the Open WebUI database

```bash
docker exec open-webui sh -c 'python3 -c "
import sqlite3
conn = sqlite3.connect(\"/app/backend/data/webui.db\")
c = conn.cursor()
c.execute(\"SELECT id, base_model_id, name, is_active FROM model\")
for row in c.fetchall():
    print(f\"  id={row[0]}, base={row[1]}, name={row[2]}, active={row[3]}\")
c.execute(\"SELECT COUNT(*) FROM model\")
print(f\"Total: {c.fetchone()[0]} models\")
"'
```

### 3. Look for the model ID prefix mismatch

Open WebUI stores models with a `model-` prefix in the `id` column (e.g. `model-qwen36-35b-mtp`) but the `base_model_id` column has the bare name (`qwen36-35b-mtp`). The chat completion endpoint at `chat_completion:2015` sometimes looks up by the wrong variant. This is a known bug in Open WebUI v0.9.x.

**Check if this is the trigger:**
- Models exist in the DB (step 2 returns rows)
- `GET /api/models` from the UI works
- User sees models in the dropdown
- But sending a message returns "Model not found" on the server log

**Fix:** Delete stale model entries, then force re-discovery via a manual re-insertion:

```bash
# 1. Delete stale model entries
docker exec open-webui sh -c 'python3 -c "
import sqlite3, time
conn = sqlite3.connect(\\\"/app/backend/data/webui.db\\\")
c = conn.cursor()

# Get user ID
c.execute(\\\"SELECT id FROM user LIMIT 1\\\")
user_id = c.fetchone()[0]

# Get actual model IDs from the backend
import urllib.request, json
resp = urllib.request.urlopen(\\\"http://host.docker.internal:9292/v1/models\\\", timeout=5)
backend_models = [m[\\\"id\\\"] for m in json.load(resp)[\\\"data\\\"]]
print(f\\\"Backend has {len(backend_models)} models: {backend_models}\\\")

# Delete all existing models
c.execute(\\\"DELETE FROM model\\\")

# Re-insert with bare names (no model- prefix — fixes v0.9.x lookup bug)
now = int(time.time())
for mid in backend_models:
    c.execute(\\\"INSERT INTO model (id, user_id, base_model_id, name, meta, params, created_at, updated_at, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\\\",
        (mid, user_id, mid, mid, \\\"{}\\\", \\\"{}\\\", now, now, 1))
    print(f\\\"  Added: {mid}\\\")

# Update config table model_ids list
row = c.execute(\\\"SELECT id, data FROM config LIMIT 1\\\").fetchone()
config = json.loads(row[1])
config[\\\"openai\\\"][\\\"model_ids\\\"] = backend_models
config[\\\"openai\\\"][\\\"api_configs\\\"][\\\"0\\\"][\\\"model_ids\\\"] = backend_models
c.execute(\\\"UPDATE config SET data = ?, updated_at = ? WHERE id = ?\\\", (json.dumps(config), now, row[0]))

conn.commit()
print(f\\\"Updated config with {len(backend_models)} model IDs\\\")
\"'

# 2. Sync the in-memory cache via /openai/config/update API
# (Login token required — see SKILL.md "Syncing Model IDs" for full flow)

# 3. Restart
docker restart open-webui
```

**Verify:**
```bash
docker exec open-webui sh -c 'python3 -c \"import sqlite3; c=sqlite3.connect(\\\"/app/backend/data/webui.db\\\").cursor(); c.execute(\\\"SELECT id FROM model\\\"); print(\\n.join([r[0] for r in c]))\"'
```

**Note:** Open WebUI does NOT auto-populate the `model` table on restart. Deleting models + restarting = still 0 models. You MUST re-insert them manually (step above) or trigger a re-sync via `/openai/config/update`.

### 4. Check if the model table is stale vs config table

Open WebUI caches model IDs in TWO separate places:
1. **`config` table** (SQLite) — the `api_configs` JSON blob
2. **`model` table** (SQLite) — per-model registry entries

Both must be in sync. See SKILL.md section "Syncing Model IDs After Fleet Changes" for the full sync procedure.

### 5. Check chat metadata for model association

```bash
docker exec open-webui sh -c 'python3 -c "
import sqlite3, json
conn = sqlite3.connect(\"/app/backend/data/webui.db\")
c = conn.cursor()
c.execute(\"SELECT id, title, chat FROM chat ORDER BY id DESC LIMIT 5\")
for row in c.fetchall():
    try:
        chat_data = json.loads(row[2])
        model = chat_data.get(\"model\", \"unknown\")
        print(f\"  Chat: {row[1][:40]} -> model={model}\")
    except:
        print(f\"  Chat: {row[1][:40]} -> JSON_PARSE_ERROR\")
"'
```

If `model=unknown`, the chat was created without a proper model association — likely from the `model-` prefix mismatch or a stale model that was deleted.

## Known Bug References

| Issue | Description | Fix |
|-------|-------------|-----|
| Discussion #21617 | Intermittent "Model not found" in v0.7.2 — model list not synced across workers with Redis | Enable "Cache Base Model List" in connections, restart |
| Issue #20340 | Model Presets send undefined as model ID, causing 404/400 | Fixed in later versions — update Open WebUI |
| v0.9.x prefix bug | `model-` prefix in DB vs bare name in API calls | Delete + re-discover models (step 3 above), or update to latest |

## Root Cause Checklist

- [ ] Backend (llama-swap/llama.cpp) reachable from container
- [ ] Models exist in `model` table
- [ ] Models exist in `config` table `api_configs`
- [ ] `base_model_id` values match actual model IDs from the backend
- [ ] Model IDs use bare names (no `model-` prefix) in `model.id` column
- [ ] Config `model_ids` list matches backend model IDs (stale whitelist blocks new models)
- [ ] Chat metadata has non-null `model` field
- [ ] `OPENAI_API_BASE_URL` ends with `/v1`
- [ ] `OPENAI_API_KEY` is non-empty
- [ ] Container restarted after model changes
