Stable error codes shared by both apps. Every user-surfaced error maps to one concrete next action (AC2.2). EN/FA message strings live in each app's localization resources keyed by these codes (Android: strings.xml / values-fa/strings.xml; Windows: Strings/*/Resources.resw + the fallback table in Strings.cs).
Codes are never renamed or reused. This is the complete Phase 2 taxonomy; new codes may be appended in later phases (e.g. WireGuard in Phase 3).
- Error β the session cannot continue; shown on the
Errorsurface with a next action. - Warning β non-blocking; sharing/connecting proceeds, shown as a banner the user can dismiss.
- Transient β handled automatically by the bounded reconnect policy (reconnect.md); only surfaces as an Error if the retry budget is exhausted.
| Code | Severity | Condition | Next action shown to user |
|---|---|---|---|
HOTSPOT_OFF |
Error | No usable IPv4 interface after waiting ~8 s for one to appear (LinkWait), and nothing else to say β the three codes below are the more specific cases, chosen by LinkSnapshot.diagnose. A shared Wi-Fi/LAN and a USB tethering link both count, not only the phone's hotspot. Before 2.8.2 this failed on the first look, so turning USB tethering on and opening Relay raced the interface coming up |
Connect the phone to Wi-Fi, turn on its hotspot, or plug in a cable and turn on USB tethering, then try again |
LINK_NEGOTIATING |
Error | A link is up and still has no IPv4 after the whole LinkWait window. Almost always USB tethering, which takes a few seconds to get an address |
Wait a moment and try again β deliberately the only one of these that does not ask the user to change anything, because the thing they would be asked to do is already done |
ONLY_MOBILE_DATA |
Error | Every addressed link is the carrier's (rmnet, ccmni, pdp, β¦). A PC cannot route to mobile data |
Connect the phone to Wi-Fi, turn on its hotspot, or plug in a cable and turn on USB tethering |
ONLY_VPN |
Error | The only addressed link is the phone's own VPN tunnel (tun, ipsec). A PC cannot reach the phone through it |
Connect the phone to Wi-Fi, turn on its hotspot, or plug in a cable. The message says explicitly that the VPN stays on and Relay will share it β turn off your VPN is the one thing this must never imply, since sharing it is the point |
HOTSPOT_LOST |
Transient β Error | Hotspot interface dropped and did not return within the reconnect bound | Check the hotspot is still on, then start sharing again |
PORT_IN_USE |
Error | Every candidate SOCKS port is bound by another app | Close the other app using those ports, then try again |
SERVICE_FAILED |
Error | Foreground service stopped unexpectedly β Android killing it for battery optimisation, or the app swiped from recents. Declared, mapped in the UI, and raised by nothing: as of 2.8.5 no code path produces it. Kept because codes are never removed, and because the condition is real even if nothing detects it yet | Allow Relay to ignore battery optimisation in Advanced, then start sharing again |
WG_START_FAILED |
Error | The WireGuard endpoint could not start on the phone | Start sharing again |
NO_VPN_ACTIVE |
Warning | No VPN is active on the phone when sharing starts | Informational: you're sharing your regular connection. Turn on your VPN first if you meant to share it |
VPN_LOCKDOWN_ON |
Warning | The same fault as PC_GOT_NO_REPLY, with the cause confirmed: Settings.Secure.always_on_vpn_lockdown reads non-zero (VpnLockdown). The only warning that gets an action button, because it is the only one that knows a screen worth opening |
Open VPN settings (button), tap the β beside the VPN app's name, turn "Block connections without VPN" off. The message says explicitly that the VPN keeps running |
PC_GOT_NO_REPLY |
Warning | A PC took a configuration and the WireGuard endpoint saw no completed handshake in the 20 s that followed (HandshakeWatch). Fires at the same moment the PC shows ERR_WG_NO_HANDSHAKE. Replaces 2.8.1's VPN_CAPTURES_RELAY, which predicted the fault from a route lookup and was seen announcing it on a session that then paired and carried traffic β see vpn-compat.md |
Exclude Relay in the VPN app's per-app list, or turn off "Block connections without VPN" in Settings β Network β VPN |
BATTERY_UNRESTRICTED_DENIED |
Warning | Battery-optimization exemption not granted | Allow it so sharing survives screen-off (button opens the exemption dialog) |
Input-validation codes (bad scan / bad typed code) never touch the system and render locally without entering the shared state machine; the rest arrive through the state machine's failure transition.
| Code | Severity | Condition | Next action shown to user |
|---|---|---|---|
ERR_QR_INVALID |
Error | Scanned QR isn't a Relay payload (decode/validation failed) | Show the QR from the Relay app on the phone and try again |
ERR_QR_NEWER_VERSION |
Error | Payload v is newer than this client supports |
Update the Windows app |
ERR_CODE_INVALID |
Error | Typed code isn't a shape the scheme could have issued (two digits, or the long code's length/alphabet/checksum) | Re-check the code on the phone and try again |
ERR_CODE_NOT_FOUND |
Error | No phone on this network is announcing the two digits that were typed | Check the phone is still sharing and this PC is on its hotspot, the same Wi-Fi, or its USB cable with tethering on |
ERR_CODE_AMBIGUOUS |
Error | More than one phone is announcing that code (see /shared/pairing-beacon.md) |
Stop sharing on the phone you don't want, then try again |
ERR_FULL_MODE_NEEDS_QR |
Error | The phone is sharing but is not offering a pairing port, so a code cannot fetch a configuration | Scan its QR code instead |
ERR_HOST_UNREACHABLE |
Error | SOCKS5 probe to the phone failed on first connect (proxy rolled back) | Put this PC on the phone's Wi-Fi or hotspot, or connect it by USB with tethering on, then try again |
ERR_WRONG_NETWORK |
Error | The phone's host IP is not on any connected interface's subnet | This PC shares no network with the phone. Join its Wi-Fi or hotspot, or plug in the cable and turn on USB tethering, then try again |
ERR_CONNECTION_LOST |
Transient β Error | An established connection dropped and did not recover within the reconnect bound | The phone became unreachable. Re-check the hotspot and connect again |
ERR_FIREWALL_BLOCKED |
Error | Local connect refused/blocked in a way consistent with a firewall rule | Allow Relay through Windows Firewall (or your security software), then try again |
ERR_PROXY_APPLY_FAILED |
Error | Applied proxy didn't verify on read-back (rolled back) | Close other proxy/VPN managers and try again |
ERR_ROLLBACK_INCOMPLETE |
Error | Disconnect couldn't restore the snapshot (backup kept) | Press Disconnect again to retry the restore |
ERR_WG_ELEVATION_DECLINED |
Error | The elevation prompt was dismissed | Choose Yes on the prompt to connect |
ERR_WG_START_FAILED |
Error | The tunnel process did not come up (no adapter, or the configuration was refused) | Close any other VPN, then try again |
ERR_WG_ELEVATION_UNAVAILABLE |
Error | Windows refused to elevate from Relay's install folder, so no prompt was ever shown β seen when %LOCALAPPDATA%\Programs is a junction to another drive |
Reinstall Relay somewhere that is not redirected |
ERR_PHONE_REPLIES_BLOCKED |
Error | The phone's own beacon carries blocked: "replies" β it knows a pairing cannot complete. Raised before the attempt, so the click does not spend 20 s reaching ERR_WG_NO_HANDSHAKE, whose advice ("scan the QR") is wrong in this case. Not enforced: the beacon refreshes once a second, so fixing it on the phone and clicking again goes straight through. See /shared/pairing-beacon.md β "blocked" |
On the phone: Settings β Network β VPN β β beside the VPN app β turn off "Block connections without VPN". The VPN itself keeps running |
ERR_WG_NO_HANDSHAKE |
Error | The adapter came up but the peer never handshaked β almost always a QR whose keys the phone has since replaced | Scan the QR the phone is showing now |
ERR_PAIRING_DENIED |
Error | The person holding the phone declined this computer, or did not answer within 60 s | Tap Allow on the phone, then try the code again |
ERR_PAIRING_VERSION |
Error | The phone speaks an older pairing version than this PC | Update Relay on the phone |
ERR_WG_ALREADY_RUNNING |
Error | A tunnel is already up; a second would fight it for the adapter | Disconnect first |
ERR_WG_STOP_FAILED |
Error | The tunnel process would not exit | Restart Relay; the adapter and its routes go when it exits |
ERR_CAMERA_DENIED |
Error | Camera unavailable or access denied | Allow camera access for desktop apps in Windows Settings β Privacy, or enter the code manually |
An empty phone list on Windows is not a failure β nothing has gone wrong yet β
but "Open Relay on your phone and tap Start Sharing" is right for only one of
three situations. DiscoveryHealth picks between them, and each has a different
next action:
| State | Condition | What it says |
|---|---|---|
NotListening |
LanDiscovery.Start() threw β port 47654 taken, or a policy forbids the bind |
Two digits cannot work here; scan the QR or use the longer code |
NoNetwork |
No non-loopback, non-link-local IPv4 on any adapter that is up. 169.254/16 counts as no network: it is what Windows assigns when DHCP never answered | Join the phone's hotspot, its Wi-Fi, or plug in the cable |
Listening |
Listening, on a network, nothing heard yet | The original message, unchanged |
NotListening used to be a log line only. It is survivable β QR and the
eight-character code both still work β which is why it was never surfaced, and
that was the mistake: a fallback only helps somebody who knows to reach for
it.
- One next action per message. No dead-ends, no raw exception text.
- Actionable, not diagnostic. The user is told what to do, not what failed internally.
- Transactional safety first. Any error that occurs after the proxy was applied rolls it back before surfacing, so a failure never leaves the system half-configured (safety invariant #2). The one exception is a transient drop inside the reconnect window, where the proxy is intentionally held (see ADR-0007).
- Transient handling is invisible until it fails. The reconnect policy runs silently; the user only sees
HOTSPOT_LOST/ERR_CONNECTION_LOSTif the bounded budget is exhausted.