HydraIssues

iPad pairing fails silently when Sunshine named_devices accumulates (wrong UUID in unpair)
open bug Project: hydraheadipad Reporter: claude-agent 24 May 2026 09:04

Description

## Summary

iPad pairing with Sunshine fails silently when the `sunshine_state.json` named_devices list has accumulated prior entries. Two interacting bugs cause this. **Confirmed affecting all Windows body nodes as of 2026-05-24. All bodies cleared 2026-05-30.**

---

## Root Cause

### Bug A — HydraPairSession uses hardcoded UUID for unpair (hydra-moonlight-ios)

**Diagnosis revised 2026-05-30 — this bug does not exist.** `HydraPairSession.m` uses `@"0123456789ABCDEF"` consistently with `HttpManager._uniqueId`. Both values match, so unpair requests are addressed correctly. `[IdManager getUniqueId]` would return an empty string in this context (key never set), which would make things worse. The original diagnosis was incorrect.

---

### Bug B — named_devices accumulates without bound

Sunshine has no TTL or size limit on `named_devices`. Every pair attempt by any head appends a new entry. Cross-district pairing attempts from cheeky-cactus-86 (bxl1) to cosmic-pretzel-98 (bxl1-test) were one driver, but accumulation also occurs from legitimate re-pair cycles.

Once `sunshine_state.json` grows large enough, the Sunshine web API (port 47990) stops responding with JSON and returns HTML — the `/welcome` first-run setup page. hydrabody then fails with: `parsing apps: invalid character '<' looking for beginning of value` and reports `provider_status: sunshine_api_unreachable`.

**Credential loss on state deletion:** Deleting `sunshine_state.json` also removes the stored web UI credentials. Sunshine reverts to first-run state. The `/api/password` endpoint cannot be used to recover at this point. `sunshine.exe --creds` is the correct tool, but requires a two-pass sequence — see workaround below.

---

## Node State History

| Body | Entries (2026-05-24) | Entries (2026-05-30) | Status |
|------|---------------------|---------------------|--------|
| boom-pickle-38 (node-74f9fbf2) | 294 / 408 KB | 0 | Fixed 2026-05-30 |
| chunky-turnip-23 (node-b961f1c8) | 190 / 253 KB | 0 | Fixed 2026-05-30 |
| fluffy-dumpling-87 (node-11da9ea3) | 94 / 110 KB | ~100+ | Auto-trim active via v2.0.64 |
| cosmic-pretzel-98 (node-4c2be4b0) | 251 / 332 KB | 0 | Fixed 2026-05-24 |

---

## Operational Workaround (per body)

Apply when `provider_status = sunshine_api_unreachable` or iPad pairing silently returns "cert 0 bytes".

**Check first:** verify `stream_status = idle`. Never apply to a streaming node.

```powershell
# 1. Stop Sunshine and clear state
Stop-Process -Name sunshine -Force -ErrorAction SilentlyContinue
Stop-Service SunshineService -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
Copy-Item C:\Sunshine\config\sunshine_state.json C:\Sunshine\config\sunshine_state.json.bak -Force
Remove-Item C:\Sunshine\config\sunshine_state.json -Force

# 2. First --creds pass (Sunshine stopped)
C:\Sunshine\sunshine.exe --creds sunshine sunshine
# Expected output: "New credentials have been created"

# 3. Wait for hydrabody to restart Sunshine (~30s)
# Sunshine will be in /welcome state at this point — that is expected

# 4. Second --creds pass (Sunshine live)
C:\Sunshine\sunshine.exe --creds sunshine sunshine
# This writes sunshine_state.json with credentials while Sunshine is running

# 5. Kill Sunshine — hydrabody restarts it and picks up the new state
Stop-Process -Name sunshine -Force
```

Verify: `GET /api/v1/nodes/<nodeId>` on hydracluster — expect `provider_status: running`.

**Why two passes:** The first `--creds` run (Sunshine stopped) creates the credential file. Hydrabody then starts Sunshine which initialises but stays in /welcome state because the state file format from `--creds` alone is not sufficient. The second `--creds` run against the live instance writes the full `sunshine_state.json`. A final restart picks it up cleanly.

---

## Operational Fix Log

### cosmic-pretzel-98 — 2026-05-24
- sunshine_state.json: 332 KB, 251 entries
- Cleared state, ran `--creds`, hydrabody restarted Sunshine
- provider_status: running

### boom-pickle-38 — 2026-05-30
- sunshine_state.json: 408 KB, 294 entries — was unstable (sunshine_api_unreachable) during mill opening
- Cleared state, two-pass `--creds`, kill + hydrabody restart
- provider_status: running

### chunky-turnip-23 — 2026-05-30
- sunshine_state.json: 253 KB, 190 entries
- Same two-pass procedure
- provider_status: running

---

## Structural Fix — hydrabody v2.0.64 (shipped 2026-05-30)

Added `TrimPairedClients(10)` to the idle tick in `pkg/provider/sunshine_provider.go`. On each idle API health check (every 30s when not streaming), hydrabody lists paired clients and calls `UnpairAll` if the count exceeds 10. Log line: `[sunshine] trim: N paired clients exceeds threshold 10, unpairing all`.

Heads re-pair automatically on the next session start. No user impact.

**All bodies auto-update within 6 hours of v2.0.64 release.** The manual workaround is no longer needed for bodies running v2.0.64+, and fluffy-dumpling-87 (~100 entries) will be trimmed automatically without manual intervention.

---

## Open Items

1. **Sunshine fork:** Consider adding a max-entry cap or TTL on `named_devices` upstream — belt-and-suspenders protection if hydrabody trim ever misses a node.

2. **hydraheadflatscreen / body discovery:** Investigate why cheeky-cactus-86 (bxl1) was sending pairing requests to cosmic-pretzel-98 (bxl1-test). Body assignment must be district-scoped.