Summary
When switching to a model that isn't in the _PROVIDER_MODELS curated list via /model <provider>/<model> from a messaging gateway (Telegram/Discord), Hermes stores the raw provider/model slug as the session override and sends it verbatim as the model field in the HTTP request body, causing the provider API to reject it with HTTP 400.
Environment
- Hermes Agent v0.8.0 (2026.4.8)
- Python 3.11.15, Ubuntu 24.04 LTS
- Platform: Telegram gateway (polling mode)
- Provider: Z.AI Coding Plan (
https://api.z.ai/api/coding/paas/v4/)
Steps to reproduce
- Configure Z.AI Coding Plan credentials in
~/.hermes/.env:
GLM_API_KEY=<your-zai-coding-plan-key>
GLM_BASE_URL=https://api.z.ai/api/coding/paas/v4/
- Run
hermes gateway restart.
- In Telegram, send:
/model zai/glm-5.1
- Hermes acknowledges the switch with a warning:
Model switched to zai/glm-5.1
Provider: auto
Warning: zai/glm-5.1 was not found in this provider's model listing. It may still work if your plan supports it.
Similar models: glm-5.1, glm-5, glm-4.7
(session only -- add --global to persist)
- Send any message (e.g.
hi).
- The agent fails with:
Non-retryable error (HTTP 400): HTTP 400: Unknown Model, please check the model code.
Error code: 400 - {'error': {'code': '1211', 'message': 'Unknown Model, please check the model code.'}}
Expected vs actual
|
Expected |
Actual |
HTTP body model field |
"glm-5.1" |
"zai/glm-5.1" |
| API response |
200 with completion |
400 Unknown Model (code 1211) |
Evidence: raw request dump
From ~/.hermes/sessions/request_dump_*.json (credentials redacted):
{
"timestamp": "2026-04-11T19:41:24.796138",
"reason": "non_retryable_client_error",
"request": {
"method": "POST",
"url": "https://api.z.ai/api/coding/paas/v4/chat/completions",
"headers": {
"Authorization": "Bearer <REDACTED>",
"Content-Type": "application/json"
},
"body": {
"model": "zai/glm-5.1",
"messages": ["..."]
}
},
"error": {
"status_code": 400,
"body": {"code": "1211", "message": "Unknown Model, please check the model code."}
}
}
The URL correctly resolves to the Z.AI Coding Plan endpoint, but body.model retains the zai/ provider prefix.
Root cause (two related issues)
1. Stale curated list. hermes_cli/models.py around line 125:
"zai": [
"glm-5",
"glm-5-turbo",
"glm-4.7",
"glm-4.5",
"glm-4.5-flash",
],
Z.AI /models endpoint currently returns 7 models: glm-4.5, glm-4.5-air, glm-4.6, glm-4.7, glm-5, glm-5-turbo, glm-5.1. The curated list is missing glm-5.1, glm-4.6, and glm-4.5-air. glm-5.1 was released around 2026-03-28 per its created timestamp from Z.AI.
2. Normalize bypass when model is not in curated list (the actual bug). When the user sends /model zai/glm-5.1, _handle_model_command in gateway/run.py (around line 3579) stores a session override. Because glm-5.1 is not in _PROVIDER_MODELS["zai"], the switch flow treats the input as "unrecognized but API-probed OK" — validate_requested_model() performs a live API probe to confirm the model exists, so the switch succeeds with only a warning, but the session override stores the raw zai/glm-5.1 slug. When Hermes later builds the HTTP request body, it reads the override and uses it directly without calling normalize_model_for_provider() to strip the zai/ prefix. Result: body.model = "zai/glm-5.1".
If glm-5.1 were in the curated list, the switch flow would normalize the name to bare glm-5.1 before storing. Since it isn't, normalization is bypassed.
The --provider flag works around this because /model glm-5.1 --provider zai takes a different branch that stores the bare model name from the start.
Verified workarounds
- Explicit
--provider flag: /model glm-5.1 --provider zai — ✅ verified on v0.8.0.
- Patch
_PROVIDER_MODELS["zai"] to include glm-5.1 (and ideally glm-4.6, glm-4.5-air). After this patch, /model glm-5.1 (bare, without --provider) also works correctly — ✅ verified on v0.8.0. Note this is a local mitigation that fixes the symptom for specific models but does not address the underlying normalize bypass; any newly released model will hit the same issue until the curated list is updated again.
Proposed fixes
Option A (recommended, minimal) — In _handle_model_command, after _switch_model returns, call normalize_model_for_provider() on the resolved model before storing it into _session_model_overrides. This ensures the stored override always has the bare model name regardless of curated-list membership.
Option B (defense in depth) — Normalize at the HTTP request builder so no matter how the override got there, the outbound body.model is always bare.
Option C (root fix for the stale-list symptom) — Populate _PROVIDER_MODELS[provider] dynamically via probe_api_models() or similar at startup, instead of hardcoding. Complements A and B.
Options A + C together would fix both the normalize bypass and the staleness.
Additional note
This class of bug will resurface whenever a provider adds new models faster than the hardcoded curated list can be refreshed. Z.AI, Kimi, MiniMax etc. release new models roughly monthly; the curated list cannot realistically keep pace without either (a) dynamic refresh or (b) graceful handling of unknown-but-API-probed models.
Thanks for the great project!
Summary
When switching to a model that isn't in the
_PROVIDER_MODELScurated list via/model <provider>/<model>from a messaging gateway (Telegram/Discord), Hermes stores the rawprovider/modelslug as the session override and sends it verbatim as themodelfield in the HTTP request body, causing the provider API to reject it with HTTP 400.Environment
https://api.z.ai/api/coding/paas/v4/)Steps to reproduce
~/.hermes/.env:hermes gateway restart./model zai/glm-5.1hi).Expected vs actual
modelfield"glm-5.1""zai/glm-5.1"Evidence: raw request dump
From
~/.hermes/sessions/request_dump_*.json(credentials redacted):{ "timestamp": "2026-04-11T19:41:24.796138", "reason": "non_retryable_client_error", "request": { "method": "POST", "url": "https://api.z.ai/api/coding/paas/v4/chat/completions", "headers": { "Authorization": "Bearer <REDACTED>", "Content-Type": "application/json" }, "body": { "model": "zai/glm-5.1", "messages": ["..."] } }, "error": { "status_code": 400, "body": {"code": "1211", "message": "Unknown Model, please check the model code."} } }The URL correctly resolves to the Z.AI Coding Plan endpoint, but
body.modelretains thezai/provider prefix.Root cause (two related issues)
1. Stale curated list.
hermes_cli/models.pyaround line 125:Z.AI
/modelsendpoint currently returns 7 models:glm-4.5,glm-4.5-air,glm-4.6,glm-4.7,glm-5,glm-5-turbo,glm-5.1. The curated list is missingglm-5.1,glm-4.6, andglm-4.5-air.glm-5.1was released around 2026-03-28 per itscreatedtimestamp from Z.AI.2. Normalize bypass when model is not in curated list (the actual bug). When the user sends
/model zai/glm-5.1,_handle_model_commandingateway/run.py(around line 3579) stores a session override. Becauseglm-5.1is not in_PROVIDER_MODELS["zai"], the switch flow treats the input as "unrecognized but API-probed OK" —validate_requested_model()performs a live API probe to confirm the model exists, so the switch succeeds with only a warning, but the session override stores the rawzai/glm-5.1slug. When Hermes later builds the HTTP request body, it reads the override and uses it directly without callingnormalize_model_for_provider()to strip thezai/prefix. Result:body.model = "zai/glm-5.1".If
glm-5.1were in the curated list, the switch flow would normalize the name to bareglm-5.1before storing. Since it isn't, normalization is bypassed.The
--providerflag works around this because/model glm-5.1 --provider zaitakes a different branch that stores the bare model name from the start.Verified workarounds
--providerflag:/model glm-5.1 --provider zai— ✅ verified on v0.8.0._PROVIDER_MODELS["zai"]to includeglm-5.1(and ideallyglm-4.6,glm-4.5-air). After this patch,/model glm-5.1(bare, without--provider) also works correctly — ✅ verified on v0.8.0. Note this is a local mitigation that fixes the symptom for specific models but does not address the underlying normalize bypass; any newly released model will hit the same issue until the curated list is updated again.Proposed fixes
Option A (recommended, minimal) — In
_handle_model_command, after_switch_modelreturns, callnormalize_model_for_provider()on the resolved model before storing it into_session_model_overrides. This ensures the stored override always has the bare model name regardless of curated-list membership.Option B (defense in depth) — Normalize at the HTTP request builder so no matter how the override got there, the outbound
body.modelis always bare.Option C (root fix for the stale-list symptom) — Populate
_PROVIDER_MODELS[provider]dynamically viaprobe_api_models()or similar at startup, instead of hardcoding. Complements A and B.Options A + C together would fix both the normalize bypass and the staleness.
Additional note
This class of bug will resurface whenever a provider adds new models faster than the hardcoded curated list can be refreshed. Z.AI, Kimi, MiniMax etc. release new models roughly monthly; the curated list cannot realistically keep pace without either (a) dynamic refresh or (b) graceful handling of unknown-but-API-probed models.
Thanks for the great project!