---
name: adb-over-tailscale
description: Connect to a remote Android device over Tailscale using ADB wireless debugging — pairing flow, common operations (notifications, packages, logcat), pitfalls when the device sleeps or ADB goes offline, and the launcher intent that actually works for Health Connect.
---

# ADB over Tailscale to a remote Android device

Use this when the user wants Hermes (or any local workstation) to drive a phone that is **not on the same LAN** as the workstation. Tailscale gives a stable IP across networks, and ADB wireless-debugging gives the channel.

## When to use

- The phone is on cellular or a different LAN from the workstation
- USB is impractical (long-distance, shared device, locked cabinet)
- The user is running Hermes on a server (Oracle VM, AWS, etc.) and wants to push/pull from a phone at home

## When NOT to use

- The phone is on the same LAN — `adb connect <LAN-IP>:<port>` directly, no Tailscale hop
- You need a realtime control loop for a wearable companion — see `android-watch-companion-bridges` (ADB over Tailscale is too slow and drops when the phone sleeps)
- The user only needs to push APKs occasionally — consider a cron-driven `adb install` instead

## Step 1 — Pair (first time or after phone reboot)

The user must enable **Wireless debugging** on the phone:
Settings → Developer Options → Wireless debugging → note the **pairing code** and **pairing port**

```bash
adb pair <TAILSCALE_IP>:<PAIRING_PORT> <PAIRING_CODE>
```

**Pitfall: the pairing port and connection port are DIFFERENT.** After pairing, the phone screen shows a separate "IP address & port" for the actual connection. Always ask the user for both. The pairing port/code expires quickly and may close after the dialog is dismissed; if `adb pair` says `Unable to start pairing client` or `nc` shows the pairing port refused, ask for a fresh pairing dialog with both code and pairing port.

Quick reachability check before retrying:
```bash
nc -vz -w 3 <PHONE_TAILSCALE_IP> <PAIRING_PORT>
nc -vz -w 3 <PHONE_TAILSCALE_IP> <CONNECTION_PORT>
```

## Step 2 — Connect

```bash
adb connect <TAILSCALE_IP>:<CONNECTION_PORT>
adb devices   # should show "device" (not "offline")
```

If the screenshot of Wireless Debugging shows a LAN IP (`192.168.x.x:<port>`), try the same port on the phone's Tailscale IP and verify with `nc`. If `adb connect` times out but `nc` succeeds, restart the local ADB server and retry:

```bash
adb kill-server
adb start-server
adb connect <TAILSCALE_IP>:<CONNECTION_PORT>
adb devices -l
```

**Pitfall: ADB shows "offline" when the phone is locked.** The user must unlock the phone and keep the screen on for ADB to work. Wait a few seconds after unlock before retrying.

## Step 3 — Verify

```bash
adb devices
# Expected: <TAILSCALE_IP>:XXXXX  device
```

## Common operations

### Read active notifications (detailed)

```bash
adb shell dumpsys notification --noredact
```

**WARNING:** Output is huge and mixes text from multiple notifications. Extract specific apps:

```bash
# Filter for financial apps
adb shell dumpsys notification --noredact | grep -iE "bbva|uala|brubank|mercado|naranja|prex|banc|transfer|pesos" -A 5

# Filter by package
adb shell dumpsys notification --noredact | grep "pkg=com.bbva.nxt_argentina" -A 30
```

**CRITICAL:** Do NOT present notification text as confirmed transaction data without verifying. The dump output can be garbled, mixing content from multiple sources. Cross-reference with Gmail or other sources before reporting financial figures.

### List installed packages

```bash
# Third-party apps only
adb shell pm list packages -3

# Filter by keyword
adb shell pm list packages -3 | grep -iE "bank|pay|wallet|crypto"
```

### Check enabled notification listeners

```bash
adb shell settings get secure enabled_notification_listeners
```

### Grant notification listener permission (no root needed)

```bash
adb shell cmd notification allow_listener <package>/<service_class>
```

**Pitfall:** This only works via ADB; cannot be done programmatically from within the app itself without user action in Settings.

### Check app details

```bash
adb shell dumpsys package <package_name> | head -50
```

### Read app logs

```bash
adb logcat -d --pid=$(adb shell pidof <package_name>) | tail -30
```

### Check network connections

```bash
adb shell cat /proc/$(adb shell pidof <package_name>)/net/tcp6
```

## Health Connect — the only working intent

Health Connect on Pixel lives at two packages:
- `com.google.android.apps.healthdata` — main Health Connect app (no launcher activity)
- `com.google.android.healthconnect.controller` — controller with activities

**Opening Health Connect from ADB:**
```bash
# The only working intent (healthconnect:// URI and Settings intent both fail)
adb shell am start -n com.google.android.healthconnect.controller/com.android.healthconnect.controller.navigation.TrampolineActivity
```

**Granting permissions for a specific app:**
```bash
adb shell am start -n com.google.android.healthconnect.controller/com.android.healthconnect.controller.permissions.request.PermissionsActivity --es calling_package <YOUR_APP_PACKAGE>
```

**Checking Health Connect status:**
```bash
# Is HC installed?
adb shell pm list packages | grep health

# Our app's granted HC permissions
adb shell dumpsys package com.google.android.apps.healthdata | grep -A2 "<your-app-id>"
```

**Pitfall:** `healthconnect://com.google.android.apps.healthdata` URI scheme does NOT resolve. `android.settings.HEALTH_CONNECT_SETTINGS` intent also fails. Both approaches throw "Activity not found". Always use the explicit component name above.

## Limitations

- ADB over WiFi drops when phone sleeps — need screen on + unlocked
- Wireless debugging port changes each session — need fresh pairing code
- `dumpsys notification` only shows CURRENT notifications, not history
- Notification listener permission is per-user profile (User 0 vs work profile)
