Fixes
Fix: Hermes Agent HTTP 400 on the first chat
Install succeeded, hermes doctor is clean, and then the first real message dies:
Error: provider returned HTTP 400
A 400 is the provider telling you the request was malformed — not that Hermes is broken and not that your key is bad. In practice it is almost always the model identifier.
Why the model name is the usual suspect
Hermes passes whatever model string is in your config straight through to the
provider. Providers do not guess. claude-sonnet-4 when the catalogue says
claude-sonnet-4-5, gpt-4o on a provider that namespaces it as openai/gpt-4o,
or a dated snapshot that has since been retired — each produces a 400 with a
message that Hermes may or may not surface in full.
Step 1 — isolate the failure
Get tools, gateways and MCP servers out of the picture first:
hermes chat -q "hello"
If this succeeds, your model config is fine and the 400 is coming from something in the tool or gateway layer — a different problem entirely. If it fails, continue.
Step 2 — see what Hermes is actually sending
hermes model
This shows the configured provider and model string. Read it character by character. The failure mode here is that the value looks right at a glance.
To see the raw config rather than the formatted view:
hermes config path
cat "$(hermes config path)"
Step 3 — compare against the provider’s catalogue
Open your provider’s model list and find the exact identifier. Three things to check:
- Vendor prefix. Aggregators like OpenRouter namespace models as
vendor/model. The bare name will not resolve. - Date suffix. Some identifiers carry a snapshot date. Providers retire old snapshots, and a config that worked for months starts 400ing the day one is removed.
- Access. Some models require tier access even when the identifier is valid. If the name is verifiably correct and it still 400s, check whether your account can actually reach that model.
Step 4 — fix it and retest
hermes model
Run interactively, pick the corrected model, then confirm:
hermes chat -q "hello"
When it isn’t the model name
If the model string is confirmed correct against the catalogue, work through these in order.
Context budget larger than the model allows. If your configured context window exceeds what the model supports, the request is malformed before it leaves. Drop the context setting to something conservative and retest — if the 400 disappears, that was it.
Wrong base URL for the provider. A local endpoint like Ollama or vLLM pointed at an OpenAI-shaped path that it does not implement produces a 400 rather than a 404. Confirm the endpoint responds on its own:
curl -s http://localhost:11434/v1/models | head
Stale environment overriding the config file. An exported variable from an earlier experiment wins over the config file and is invisible when you read the config:
hermes config env-path
env | grep -i -E 'hermes|openai|anthropic|openrouter'
Unset anything that does not belong, open a fresh shell, and retest.
Reading the actual provider message
Hermes truncates provider errors by default. The full body usually names the exact field that was rejected, which turns guessing into reading:
hermes chat -q "hello" --verbose
If your build supports debug logging, that output is worth capturing before you open an issue — a 400 with the provider’s own message attached gets answered far faster than one without.
FAQ
Could a 400 mean my API key is wrong?
Usually not. An invalid or expired key returns 401, and a key without credit or quota returns 402 or 429. A 400 means the request itself was malformed or referenced something that does not exist — the model identifier is the most common culprit.
Why did it work yesterday and 400 today?
Providers retire dated model snapshots. If your config pins a model with a date suffix and that snapshot was deprecated, the identifier stops resolving and every request 400s from then on. Repoint it at the current identifier.
I'm on OpenRouter and copied the name from their site. Still 400.
OpenRouter model IDs are namespaced as `vendor/model`. Copying only the display name drops the vendor prefix, and the request fails. Copy the slug from the model's API tab, not the heading on the page.