# Provider Credentials & Session State Diagnosis

Diagnosis flow for HTTP 401 errors and "No LLM provider configured" when the web UI has the agent source installed (agent dir shows `[ok]` in logs).

## Diagnosis Flow

### Step 1 — Check what provider the web UI is actually trying to use

```bash
docker exec hermes-webui cat /home/hermeswebui/.hermes/config.yaml
```

The `model.provider` field is the provider the web UI will attempt to use. If this says `openrouter` but your API keys are for `deepseek`, that's the problem.

### Step 2 — Check that the .env has the right API key

```bash
docker exec hermes-webui cat /home/hermeswebui/.hermes/.env
```

The env var name must match what the provider expects. Common mappings:

| provider in config.yaml | env var |
|---|---|
| `deepseek` | `DEEPSEEK_API_KEY` |
| `openrouter` | `OPENROUTER_API_KEY` |
| `anthropic` | `ANTHROPIC_API_KEY` |
| `openai` / `openai-api` | `OPENAI_API_KEY` |
| `gemini` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |

### Step 3 — Check if the error is from a stale cached session

The web UI caches session metadata (model, provider, workspace) on disk. When you switch providers, old sessions keep the old provider.

```bash
# Find the most recent session file
ls -t /home/hermeswebui/.hermes/webui/sessions/*.json 2>/dev/null | head -3

# Check its model/provider
python3 -c "
import json, glob
for f in sorted(glob.glob('/home/hermeswebui/.hermes/webui/sessions/*.json')):
    with open(f) as fh:
        d = json.load(fh)
    print(f'{f}: model={d.get(\"model\")}  provider={d.get(\"model_provider\")}')
"
```

Expected: `model=deepseek-v4-flash provider=deepseek` (or whatever your current provider is).

If the session shows a different provider, update it:

```bash
python3 -c "
import json, glob
for f in glob.glob('/home/hermeswebui/.hermes/webui/sessions/*.json'):
    with open(f) as fh:
        d = json.load(fh)
    d['model'] = 'deepseek-v4-flash'       # <-- replace with your model
    d['model_provider'] = 'deepseek'        # <-- replace with your provider
    with open(f, 'w') as fh:
        json.dump(d, fh)
"
```

### Step 4 — Check settings.json

The web UI stores `default_model_provider` in settings.json:

```bash
python3 -c "
import json
with open('/home/hermeswebui/.hermes/webui/settings.json') as f:
    d = json.load(f)
print('default_model_provider:', d.get('default_model_provider'))
"
```

If this doesn't match your current provider:

```bash
python3 -c "
import json
with open('/home/hermeswebui/.hermes/webui/settings.json') as f:
    d = json.load(f)
d['default_model_provider'] = 'deepseek'   # <-- replace with your provider
with open('/home/hermeswebui/.hermes/webui/settings.json', 'w') as f:
    json.dump(d, f, indent=2)
"
```

### Step 5 — Verify provider resolution works

Test what the web UI resolves at runtime:

```bash
python3 -c "
import os
os.environ['HERMES_HOME'] = '/home/hermeswebui/.hermes'
from api.config import get_config, resolve_model_provider, model_with_provider_context

cfg = get_config()
print('Config model section:', cfg.get('model', {}))

# Simulate sending a message without specifying a model
model_with_ctx = model_with_provider_context('')
resolved = resolve_model_provider(model_with_ctx)
print('Resolved (empty model):', resolved)

# Simulate sending a message with your default model
model_with_ctx = model_with_provider_context('deepseek-v4-flash')
resolved = resolve_model_provider(model_with_ctx)
print('Resolved (default model):', resolved)

# Check runtime provider can find the API key
from api.oauth import resolve_runtime_provider_with_anthropic_env_lock
from hermes_cli.runtime_provider import resolve_runtime_provider
_rt = resolve_runtime_provider_with_anthropic_env_lock(
    resolve_runtime_provider,
    requested=resolved[1],  # the resolved provider name
)
print('API key found:', bool(_rt.get('api_key')))
"
```

If the key is found and provider/base_url are correct, the config is right and the issue is in the session cache.

## Common Patterns

### Pattern: OpenRouter → DeepSeek switch

After switching from OpenRouter to DeepSeek, three things need updating:

1. `config.yaml` — set `model.provider: deepseek`, `model.default: deepseek-v4-flash`, `model.base_url: https://api.deepseek.com/v1`
2. `.env` — set `DEEPSEEK_API_KEY=<your-key>`
3. Session cache — update session JSON files to use `model_provider: deepseek` instead of `openrouter`
4. `settings.json` — update `default_model_provider` to `deepseek`

### Pattern: Copying host config to web UI

The host's `~/.hermes/` is NOT mounted into the web UI container. The web UI uses whatever is in the bind-mounted data directory (e.g. `/home/ray/docker/hermes/data/`). To sync:

```bash
cp ~/.hermes/config.yaml /path/to/webui/data/config.yaml
grep -E '^(DEEPSEEK|OPENROUTER|ANTHROPIC|OPENAI|GEMINI|GOOGLE)_API_KEY' ~/.hermes/.env >> /path/to/webui/data/.env
```
