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.
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.
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.
| 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 |
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.
# 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.
--creds, hydrabody restarted Sunshine--creds, kill + hydrabody restartAdded 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.
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.
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.