## gatewayd-start-failed **What you see.** `gatewayd` exits 1 at start, before it connects, with one line naming its config file or a directory, then this entry. **Why.** `gatewayd` could not read or parse `gatewayd.toml`, or a value in it is not allowed: a `url` that is not `http://` or `https://` with a host and an optional port, an id that is not 26 characters of `a-z0-9`, an empty `[allow] users`, or a secret with not exactly one of `credential`, `env` and `file`. Or it could not create `/gateway/`. It will not guess at a configuration that decides who it answers. **Confirm.** The line says which: `: …` for the file (unknown keys are errors; check it against `docs/specs/2026-09-23-m4a-gateway.md`, section 3), or `cannot prepare : …` for the directory (`ls -ld "$(dirname )"`). **Fix.** Correct the file or the directory's ownership. A user id is shown in Mattermost under the user's profile, or by `GET /api/v4/users/username/`. **Check.** `gatewayd serve --config ` prints `gatewayd: connected to as `. ## secret-unavailable **What you see.** `gatewayd` exits 1 at start with `secret : `, then this entry. The value is never printed. **Why.** `gatewayd` does not start without its Mattermost token, and reads it only from the one place `[secrets.]` names. The reasons: for `credential`, `CREDENTIALS_DIRECTORY` is unset (not started by systemd with a credential) or the file in it is missing; for `env`, the variable is unset or empty; for `file`, the path is not absolute, is a symbolic link, is not a regular file, is not owned by the user `gatewayd` runs as, or has any group or other permission (only 0600 or 0400 are accepted). An empty value is refused in every form. **Confirm.** ```sh systemctl --user show -p LoadCredentialEncrypted gatewayd # credential ls -l ; id -u # file: owner and mode ``` **Fix.** For a credential: `systemd-creds --user encrypt --name= - `, type the token, then give the unit `LoadCredentialEncrypted=:`. For a file: `chmod 600 ` and `chown` it to the user `gatewayd` runs as. For an environment variable, set it in the environment `gatewayd` starts in. **Check.** `gatewayd` starts and prints `gatewayd: connected to as `. ## secret-in-a-file **What you see.** At start: `gatewayd: warning: secret is read in plaintext from ; a systemd credential keeps it encrypted at rest`, then this entry. `gatewayd` runs normally. **Why.** A file holds the token in plaintext: anyone who can read the disk, or a backup of it, can use it. A credential is encrypted to this machine's TPM and host key. Sometimes a file is right (a development machine, a system without systemd); the warning is there so the choice is deliberate. **Confirm.** `[secrets.]` in `gatewayd.toml` has `file = …`. **Fix.** To keep the file: nothing; the warning stays. To move to a credential, follow the fix in [secret-unavailable](#secret-unavailable), change the entry to `credential = ""`, restart, then delete the file and regenerate the token if the file was ever copied elsewhere. **Check.** The warning is gone at the next start. ## mattermost-unreachable **What you see.** `gatewayd` prints `gatewayd: cannot reach : ; trying again in s`, then this entry, once per attempt: after 1, 2, 5 and 10 seconds, then every 30. Posts to Boxmaker go unanswered meanwhile; they are caught up when the connection returns. **Why.** The TCP connection, the TLS handshake or the WebSocket upgrade failed, or the server went silent for `dead_after_ms`. A TLS failure means the certificate did not match the host in `url` or did not chain to the system's roots or `ca_file`; verification cannot be turned off. **Confirm.** ```sh curl -sS /api/v4/system/ping # the server answers tailscale status # for a tailnet url: the tailnet is up openssl s_client -connect :443 -servername as `, then a direct message to Boxmaker is answered. ## mattermost-auth-failed **What you see.** `gatewayd` exits 1 with `gatewayd: Mattermost refused the token ()`, then this entry. **Why.** Mattermost answered 401 or 403: the token is wrong, revoked, or belongs to a deactivated user. Retrying with the same token cannot help, so `gatewayd` stops instead. **Confirm.** With the token in `$T` (from the same place `gatewayd` reads it; do not paste it into a shared shell history): `curl -sS -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" /api/v4/users/me` prints 401 or 403. **Fix.** In Mattermost, under the bot's or user's access tokens, create a new token and revoke the old one. Store it as [secret-unavailable](#secret-unavailable) describes, then start `gatewayd`. **Check.** `gatewayd: connected to as `. ## gateway-state-damaged **What you see.** `gatewayd` exits 1 with `/gateway/state.json: `, then this entry: at start when the file cannot be read, or while running when it cannot be written (`…: cannot write: …`). **Why.** The state file records which posts were handled, which threads Boxmaker is in, and which turns were in flight. If it cannot be read or kept up to date, `gatewayd` could answer old posts twice or miss threads, so it does not guess. A missing file is a first start and is fine. **Confirm.** `ls -l "$BOXMAKER_HOME/gateway/state.json"` and `python3 -m json.tool "$BOXMAKER_HOME/gateway/state.json" >/dev/null`. **Fix.** For a write failure, free space or correct the directory's ownership (`df -h`, `ls -ld "$BOXMAKER_HOME/gateway"`), then start `gatewayd`. If only the file's ownership or mode is wrong, correct it. If the content is damaged, move it aside (`mv state.json state.json.damaged`) and start again. That is a first start: posts sent while `gatewayd` was down are not answered, and threads in channels must name Boxmaker again once. **Check.** `gatewayd` starts, and `state.json` is rewritten after the next post. ## loop-unavailable **What you see.** In the Mattermost thread: "Boxmaker's loop is not running (see docs/runbook.md#loop-unavailable)". The messages that were waiting are dropped. **Why.** `gatewayd` could not connect to `loop.sock`, or the connection closed before the turn ended. `gatewayd` does not retry: `loopd` may have finished and logged the turn, and sending it again would run it twice. **Confirm.** `ls -l "$BOXMAKER_HOME/run/loop/loop.sock"` (or `[loop] socket`), and whether `loopd serve` is running. If it stopped, its last lines say why. **Fix.** Start `loopd serve --config `; if it failed, follow the entry its message names. Then send the message again in the thread. **Check.** A direct message to Boxmaker is answered.