4.8 KiB
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
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
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.
# 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:
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:
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:
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:
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:
config.yaml— setmodel.provider: deepseek,model.default: deepseek-v4-flash,model.base_url: https://api.deepseek.com/v1.env— setDEEPSEEK_API_KEY=<your-key>- Session cache — update session JSON files to use
model_provider: deepseekinstead ofopenrouter settings.json— updatedefault_model_providertodeepseek
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:
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