Troubleshooting and FAQ¶
A "symptom → cause → fix" matrix based on real cases, then frequently asked questions.
Symptom → cause → fix¶
| Symptom | Cause | Fix |
|---|---|---|
The connection closes with code 1008, error{code:"auth_failed"} arrives |
the token in the app ≠ any of MOBILE_TOKEN / MOBILE_EXTRA_TOKENS, or device.id is absent from MOBILE_ALLOWED_DEVICES |
verify the token with the agent owner; if devices are restricted — add the device.id or remove the filter; retrying is pointless |
error{code:"forbidden"} arrives (+ 1008 on connect) |
the token is bound to a different device.id (device_id=token), someone else's id is taken by another token, or a response to a foreign device's card |
connect under your own device.id (the one bound to the token); a card is closed only by the device it is shown to |
error{code:"forbidden"} on a deliver frame |
deliver is not sent by cron: the connection is not from loopback and the token is not in MOBILE_DELIVER_TOKENS |
service delivery is performed by cron/hermes send from the same machine (loopback); for delivery from outside — add the token to MOBILE_DELIVER_TOKENS |
error{code:"too_big"} / bad_frame on a media upload |
the number of unfinished uploads (8 per device), the device's total buffer (> 8 MiB) or parts > 2048 is exceeded |
wait for the current uploads to finish; do not keep many series open; split a file larger than 4 KiB into chunks; buffers are freed on disconnect |
ready arrives, but there are no agent responses |
the device has not yet been approved by Hermes (pairing) | on the agent machine: hermes pairing list → hermes pairing approve mobile <request-id>; or, in advance, MOBILE_ALLOWED_USERS=<user id> |
| The agent responds: "No home channel is set" / about a pairing code | standard Hermes behaviour on first contact with an unfamiliar user | approve the pairing; or send /sethome so that this chat becomes the home channel (for cron delivery) |
error{code:"device_offline"} on cron delivery |
the app is not connected at the moment of delivery | mobile delivery requires a live connection: keep the app connected at the right moment or deliver to a device that is usually online (MOBILE_HOME_DEVICE) |
| Media does not arrive | a chunk series was interrupted (part/parts), or the 8 MiB limit / 16 MiB frame is exceeded |
send all parts up to parts-1 with a single id; send a large file in chunks of ~3 MiB or compress it; on too_big — the same |
| Duplicate messages on resend | a constant id is not set, or a new id is used on resend |
keep id constant for the message and repeat it on resend — the server ignores the repeat |
| The connection does not establish: 404 / 426 on the public address | the reverse proxy does not proxy WebSocket (no Upgrade), or the path is wrong |
proxy the route (handle /ws* in Caddy) with an upgrade to WS; verify the path against MOBILE_PATH (default /ws) |
The adapter does not start: bind_failed: не удалось занять 127.0.0.1:8787 |
port 8787 is taken by another process | ss -ltnp \| grep 8787; free the port or change MOBILE_PORT and update the proxy |
config_missing: MOBILE_TOKEN не задан |
no token in the profile's .env |
the platform does not start without a token (protection): add MOBILE_TOKEN to ~/.hermes/.env and restart the gateway |
deps_missing / "no websockets" |
the websockets package is unavailable in the environment |
the adapter does not install dependencies: the package is part of Hermes — update Hermes |
The plugin is not visible in hermes plugins list |
wrong path or not enabled in the config | check ~/.hermes/plugins/<name>/plugin.yaml and plugins.enabled in config.yaml |
First frame is not hello → error{code:"bad_frame"} + 1008 |
the client sent text/media before the handshake |
always send hello as the first frame |
| The card does not close / the response is "swallowed" | the card expired (prompt_done{expired:true}) or the prompt_id is wrong |
respond with a value from options immediately; once expired, the card simply closes |
Audio format not accepted: unsupported_frame |
format is not wav/ogg |
send WAV 16 kHz mono or OGG |
Unrecognized media type: unsupported_frame |
kind is outside image/video/document/audio |
use the allowed values |
Manual check
wscat -c "ws://127.0.0.1:8787/ws", then send
{"t":"hello","token":"<token>","device":{"id":"cli"}} as the first frame — ready should arrive (a token in the URL
?token= is not accepted). Then {"t":"text","id":"t1","text":"hi"}.
FAQ¶
Is the SDK required? No. The protocol is JSON frames over WebSocket; any runtime will do. The SDK (TypeScript/Python) takes care of reconnection, slicing/gluing and frame parsing.
What is session in the ready frame?
The session identifier in Hermes: mobile:<device.id>. It is also the delivery address for
hermes send --to mobile:<device.id> and cron.
Where does the history go if the app is reinstalled? Nowhere: the history and memory live on the Hermes side in the agent profile. The app is only a client.
Can I use one device.id on several phones?
Technically yes, but it will be one session for all of them. For separate dialogues, give each
device its own device.id. With a device-bound token (MOBILE_EXTRA_TOKENS=device_id=token),
someone else's id cannot be taken — forbidden will arrive.
What is MOBILE_DELIVER_TOKENS?
The deliver frame (service delivery from cron) is accepted only from a loopback address or from a connection
with a token from this list; to everyone else — forbidden. In the typical setup (cron on the same machine)
there is no need to set the variable — a loopback connection is allowed anyway.
How does MOBILE_ALLOWED_USERS differ from MOBILE_ALLOWED_DEVICES?
The former concerns users (Hermes's standard authorization and pairing), the latter concerns devices
(an additional adapter filter). These are different variables and different entities — see
How it works.
Why does the response not arrive at once, but gets "drawn in"?
If streaming is enabled on the agent side: first the message arrives in full, then edits as
message frames with edit: true and the same id. Redraw by id, otherwise you will see duplicates.
Does delivery work when the app is closed?
No. Mobile delivery (cron, hermes send) requires a live connection: otherwise
device_offline is returned. The history is not lost, though — the agent serves the session even without a mobile client.
How do I revoke access for one device?
Remove its token from MOBILE_TOKEN/MOBILE_EXTRA_TOKENS (or delete the device.id from
MOBILE_ALLOWED_DEVICES) and restart the gateway: hermes gateway restart. More — in
Security.