HydraIssues

hydrabody ForceStop missing Content-Type header causes dirty stream termination
open bug Project: hydrabody Reporter: 23 May 2026 16:27

Description

Summary

Every call to Sunshine's CloseRunningApp endpoint from hydrabody fails with:

HTTP 400: {"error":"Content type not provided","status":false,"status_code":400}

This happens because the HTTP request does not include a Content-Type: application/json header. Sunshine rejects the request, so ForceStop falls back to killing the experience process by name.

Impact

  • Every portrait stream end triggers a GPU mismatch alarm (stream_status=idle but GPU still at 98% for ~30s until the process kill lands)
  • The GPU mismatch alarm fires on the streaming monitor /api/v1/body-anomalies and in the body log
  • hydrabody logs show [stream] ForceStop failed ... falling back to process kill on every clean stream end
  • The fallback process kill does succeed (killed 2 process(es) for mercator-talks by name), so streams do eventually clean up
  • No sessions are stuck permanently — the mismatch clears within 30s

Observed log sequence (chunky-turnip-23, 2026-05-23)

10:35:17 [stream] hydracluster requested stream termination — calling ForceStop
10:35:17 [stream] TerminateStream ForceStop error: close running app: HTTP 400: {"error":"Content type not provided",...}
10:35:18 [gpu-mismatch] detected: GPU at 98% with stream status idle
10:35:48 [stream] cleanup: mercator-talks (action=kill, reason=grace period expired, pid=0)
10:35:48 [stream] ForceStop failed (close running app: HTTP 400: ...), falling back to process kill
10:35:48 [stream] killed 2 process(es) for mercator-talks by name
10:35:52 [sunshine] stream ended: mercator-talks

The same pattern appears in logs from 2026-05-22 13:26, 15:47, and 21:58 — it is consistent across all stream terminations.

Root cause

The go-sunshine SDK (or the hydrabody call site) sends the CloseRunningApp POST without a Content-Type: application/json header. Sunshine's API requires it.

Fix

Add Content-Type: application/json to the CloseRunningApp HTTP request in go-sunshine (or wherever hydrabody constructs the request). One-line change.

Related

  • VDD portrait restart: after every stream end on portrait mode, hydrabody deletes display_device.state and restarts Sunshine (~3 minute window where body cannot accept new streams). This is separate from this bug but compounds the operator experience — the dirty termination alarm fires just before the Sunshine restart window begins.
  • Streaming monitor Active Problems panel (v0.4.20) will surface orphaned bodies during the GPU mismatch window if stream_status flips to idle but hydracluster still thinks a head is connected.