HydraIssues

iPad pairing fails silently when Sunshine named_devices accumulates (wrong UUID in unpair)
open bug Project: hydraheadipad Parent: #663 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.

# 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.