> ## Documentation Index
> Fetch the complete documentation index at: https://crosslink.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Common Crosslink failures, what they usually mean, and how to debug them without weakening the security model.

# Troubleshooting

When something fails, start by locating the layer that failed:

1. Pairing and trust establishment.
2. Transport reachability.
3. Capability authorization.
4. Storage and key handling.
5. App-specific RPC logic.

That order matters because the same symptom can be produced by very different causes. A refused connection is not the same as a revoked device, and a bad SAS is not the same as a relay outage.

## Pairing issues

### Pairing code expired

**Symptom:** `PAIRING_CODE_EXPIRED`

**Cause:** Pairing codes have a 2-minute TTL and are single-use.

**Fix:**

* Generate a new pairing code.
* Complete pairing within 2 minutes.
* If you are testing a slow flow, shorten the path between scanning the QR and approving the device.

### Pairing code already used

**Symptom:** `PAIRING_CODE_USED`

**Cause:** The code was consumed by another client or by a previous attempt.

**Fix:**

* Generate a fresh code.
* Do not reuse a QR after the host has already completed pairing.
* If the client auto-retried, clear the stale state before trying again.

### Fingerprint mismatch

**Symptom:** `FINGERPRINT_MISMATCH` or "host fingerprint does not match"

**Cause:** The host fingerprint in the QR does not match the host the client actually reached.

**Security warning:** Treat this as a potential MITM or stale-QR event until proven otherwise.

**Fix:**

* Verify that the desktop host is the one you intended to pair with.
* Regenerate the QR from the current host process.
* Check whether a signaling or tunnel endpoint was swapped out underneath you.

### Pairing rejected by host

**Symptom:** `PAIRING_FAILED`

**Cause:** The host's approval callback returned `false`, timed out, or was never surfaced to the user.

**Fix:**

* Check the host logs for the rejection reason.
* Confirm the requested capabilities are allowed by policy.
* Make sure your approval UI is actually reaching the human who needs to answer.

## Connection issues

### Connection refused

**Symptom:** `CONNECTION_FAILED`

**Fix:**

1. Check the services that should be running:

   ```bash theme={null}
   curl http://127.0.0.1:8081/health
   curl http://127.0.0.1:8082/health
   ```

2. Confirm that the host and client are using the same signaling and relay URLs.

3. Confirm that the host is binding to the right interface for the mode you selected.

4. Check whether a firewall or another local process already owns the port.

### Connection timeout

**Symptom:** `CONNECTION_TIMEOUT`

**Fix:**

1. Check basic network connectivity first.
2. Increase `dialTimeoutMs` if the route is legitimately slow.
3. Confirm the route kind is valid for the network you are on.
4. If a relay is configured, verify that the relay is reachable and not just the signaling service.

### Relay unavailable

**Symptom:** `RELAY_UNAVAILABLE`

**Fix:**

1. Check the relay URL and port.
2. Check whether the relay service is actually running.
3. Confirm the relay token and any client token are correct.
4. If the devices are on the same network, try LAN first to isolate the issue.

### Signaling unavailable

**Symptom:** `SIGNALING_UNAVAILABLE`

**Fix:**

1. Check the signaling URL and port.
2. Confirm the signaling service is running.
3. Verify that the WebSocket upgrade is reaching the service.
4. If you are using per-machine dev tokens, make sure the host and service are reading the same `.crosslink-data` location.

## RPC issues

### Method not found

**Symptom:** `METHOD_NOT_FOUND`

**Fix:**

1. Check the method name.
2. Confirm the host exposes that method.
3. Confirm the client is connected to the intended host, not a stale tab or old session.

### Capability denied

**Symptom:** `CAPABILITY_DENIED`

**Fix:**

1. Check whether the capability was requested during pairing.
2. Check whether the host granted it.
3. Re-pair with the required capability if the policy was too strict.
4. If the capability should have been allowed, inspect the policy and approval hook on the host.

### RPC timeout

**Symptom:** `TIMEOUT`

**Fix:**

1. Check whether the host is responsive.
2. Look for a slow handler or a queueing bug.
3. Increase the timeout only after confirming the handler is actually healthy.

## Storage issues

### Secret storage unavailable

**Symptom:** `STORAGE_ERROR`

**Fix:**

1. Check OS keychain availability first.
2. If you are using Electron, confirm `safeStorage` is available.
3. If you deliberately allow it, set `CROSSLINK_SECRET_KEY` so the encrypted-file fallback has a fixed passphrase.
4. Use `allowPlaintextFallback: true` only for development and only when you understand the risk.

### Browser storage not encrypted

**Symptom:** Warning such as "identity seed is stored in the clear"

**Cause:** WebCrypto or IndexedDB was unavailable, or the page is not in a secure context.

**Fix:**

1. Serve the page from HTTPS or localhost.
2. Confirm the browser actually supports the needed APIs.
3. Prefer `CrosslinkClient.create()` over the plain constructor when you need the secure storage path.

## Transport issues

### WebRTC not connecting

**Symptom:** The connection falls back to relay.

**Fix:**

1. Check ICE configuration and any STUN/TURN dependencies.
2. Confirm the direct path is actually available between the peers.
3. Treat relay fallback as a valid outcome unless you specifically require a direct DataChannel.

### LAN not working

**Symptom:** Devices cannot discover or reach one another on the local network.

**Fix:**

1. Confirm both devices are on the same network segment.
2. Check the host's LAN listener port and firewall rules.
3. Confirm the host is not bound only to loopback if the phone needs to reach it.
4. If you are testing on one machine, loopback is fine; if you are testing across devices, it is not.

### Remote access not working

**Symptom:** `networkMode: "remote"` fails startup or the QR does not contain a public route.

**Fix:**

1. Run `npm run check:remote` or `node scripts/check-remote-access.mjs <port>`.
2. Confirm the router actually supports PCP, NAT-PMP, or UPnP IGD.
3. Check for carrier-grade NAT or a double-NAT setup.
4. If you configured a manual port forward, make sure the public host and external port match reality.

## Debug logging

Use structured logs to debug, not ad-hoc string concatenation. The logger must support the full interface from `@crosslink/core`.

```ts theme={null}
import { consoleLogger } from "@crosslink/core";
import { createCrosslinkServer } from "@crosslink/sdk-node";

const server = createCrosslinkServer({
  application: { id: "com.example.notes", name: "Notes" },
  logger: consoleLogger({ level: "debug" })
});
```

When you are reading logs:

* Start with the event name and the transport or capability involved.
* Look for redacted reasons, not raw request objects.
* Do not paste secrets, tokens, seeds, or private keys into logs just to "see what is happening".
* If the logger is too quiet, raise the level before you add more instrumentation.

## Practical recovery loop

If you are stuck, use this sequence:

1. Restart the host and re-check the advertised endpoints.
2. Regenerate the pairing QR.
3. Verify the SAS digits on both devices.
4. If the problem is network reachability, run the remote-access check.
5. If the problem is authorization, inspect the capability policy and approval hook.
6. If the problem is storage, verify the keychain or encrypted-file backend before looking elsewhere.
