Common errors and how to fix them, grouped by where they come from.
unauthorized on every requestMost common cause: the target server parses the body as JSON before amesh verifies it. amesh verifies the signature over the raw body, so you must use express.text() or similar and parse JSON yourself after verification. Also check that both devices are paired (amesh list) and clocks are within 30 seconds.
timestamp_out_of_rangeThe device clock is off by more than 30 seconds from the server. This is one of the few error details amesh exposes in 401 bodies (intentionally — you need to know it's clock drift, not a real auth failure). Fix clock sync (ntp, chrony) on the controller.
unsupported_versionThe client is signing with a newer protocol version than the server knows. Upgrade the server-side @authmesh/sdk.
replay_detectedThe same nonce was used twice within the 30-second window. If you're running multiple server instances, they need a shared nonce store (Redis). See Integration Recipe 3. If you're in development with one instance and still see this, a client is actually retrying with the same nonce — regenerate it per request.
"Using in-memory nonce store" warningProduction deployments with more than one instance need Redis. The in-memory store is fine for single-instance dev/staging.
SAS verification failedThe 6-digit verification codes on controller and target didn't match. Either you entered it wrong, or a MITM attempt is underway on the relay. Abort and retry pairing.
allow_list_integrity_failure (500 on server)The allow_list.json file was modified outside amesh. It's HMAC-sealed, so any tampering breaks verification. Re-pair devices to regenerate.
peer not foundPairing codes are short-lived (2 minutes). Run amesh listen again on the target to get a fresh one.
selfSig verification failed after re-initKnown issue in older versions where macOS Keychain accumulated stale keys across amesh init --force runs. Fixed in v0.3.1. Upgrade and re-run init.
On Apple Silicon, Secure Enclave requires a signed binary. Homebrew installs are signed automatically; dev builds from source won't be. macOS Keychain is the second tier and works for unsigned builds. The encrypted-file backend is a software-only fallback — not bound to hardware.
TPM not available on LinuxVerify with ls /dev/tpmrm0. You may need tss2 libraries installed (apt install tpm2-tools) and user membership in the tss group.
AUTH_MESH_PASSPHRASE requiredYou're using the encrypted-file backend with a passphrase not stored in identity.json (older setups). Either set the env var, or re-init — from v0.3.0+, amesh auto-generates a 256-bit passphrase and stores it in identity.json.
"Shell access not granted for this device"The controller is paired but doesn't have shell permission. Run amesh grant <device-id> --shell on the target. Pairing alone doesn't grant shell access — it's a separate explicit permission.
"The agent daemon requires Bun runtime for PTY support" (unsupported architectures only)You should never see this on macOS (arm64/x64) or Linux (x64/arm64) — the npm postinstall downloads a prebuilt binary that bundles Bun, and amesh-agent agent start runs directly. If you do see it on a supported platform, the postinstall probably couldn't reach GitHub releases — check the install log for download errors and re-run npm rebuild @authmesh/agent with network access.
On unsupported architectures (Raspberry Pi 3 and earlier, armv7 32-bit Pi OS), the postinstall falls back to the JS entry and the agent needs Bun for PTY. Bun does not ship for armv7, so you'd need a third-party Bun build. Most users should move to Pi 4/5 on 64-bit Pi OS or a different ARM host.
"Handshake failed" / connection timeoutThe agent is not running on the target. Start it with amesh-agent agent start, and verify the relay is reachable from both sides (port 443 or whatever your self-hosted relay uses).
"Refusing to run as root"The agent defaults to non-root for safety. If you genuinely need root shells (and understand the blast radius), start the agent with --allow-root.
"requires Bun"The relay is Bun-native (uses Bun.serve()). Install Bun from bun.com or use the provided Dockerfile which bundles Bun.
Pairing uses WebSocket and takes up to 90 seconds end-to-end. Set Cloud Run min instances ≥ 1 to avoid cold-start WebSocket drops, or increase request timeout.
When in doubt, start here:
# Show this device + all trusted peers $ amesh list # Verify clock sync (macOS) $ sntp -t 5 time.apple.com # Verify clock sync (Linux) $ timedatectl status # Test SDK signing end-to-end (no server needed) $ amesh sign-test # Check which key storage backend is active $ amesh list | grep Backend