fix(stream): health poll crashed on every start — parse the real healthStatus object shape
startup.log 2026-09-22 (via the user's run of the new build): every session
start logged 'Stream health poll failed: The requested operation requires an
element of type String, but the target element has type Object'.
Root cause: GetStreamHealthAsync did status.healthStatus.GetString(), but the
real API nests it as status.healthStatus = {status, lastUpdateTimeSeconds,
configurationIssues[]} — an OBJECT, per the liveStreams reference
https://developers.google.com/youtube/v3/live/docs/liveStreams (checked the
docs before fixing, per derivative-work rule). Our flat-string fixture was the
wrong assumption since TASK 9; the report-by-exception health banner had been
silently dead.
Fix: read the nested healthStatus.status + nested configurationIssues[], tolerating
the legacy flat-string shape so nothing else breaks. Tests flipped to the real
object shape (the Good Dog for this change). Full suite 315/315, build 0 warnings.
Also recorded in MyMistakes.md (API-shape recipe) and ai.md/Services index. End-of-session
transition(complete) 403 invalidTransition (enableAutoStop fallback) observed on all three
2026-09-22 stops — separate racy-design issue, carried, not in this change.
This commit is contained in:
@@ -231,29 +231,45 @@ public class YouTubeStreamService
|
||||
if (items.GetArrayLength() == 0) return null;
|
||||
|
||||
var status = items[0].GetProperty("status");
|
||||
var health = new StreamHealth
|
||||
// The real API nests health as status.healthStatus = { status, lastUpdateTimeSeconds,
|
||||
// configurationIssues[] }. The flat "healthStatus":"bad" shape is our old wrong
|
||||
// assumption — accept both so neither crashes the report-by-exception poll.
|
||||
var health = new StreamHealth();
|
||||
var issueElements = new List<JsonElement>();
|
||||
if (status.TryGetProperty("healthStatus", out var healthStatus))
|
||||
{
|
||||
HealthStatus = status.TryGetProperty("healthStatus", out var healthStatus)
|
||||
? healthStatus.GetString()
|
||||
: null,
|
||||
};
|
||||
if (status.TryGetProperty("configurationIssues", out var issues))
|
||||
{
|
||||
foreach (var issue in issues.EnumerateArray())
|
||||
if (healthStatus.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var severity = issue.TryGetProperty("severity", out var sev) ? sev.GetString() : null;
|
||||
var type = issue.TryGetProperty("type", out var t) ? t.GetString() : null;
|
||||
health.ConfigurationIssues.Add(new StreamConfigurationIssue
|
||||
{
|
||||
Severity = severity switch
|
||||
{
|
||||
"error" => StreamIssueSeverity.Error,
|
||||
"warning" => StreamIssueSeverity.Warning,
|
||||
_ => StreamIssueSeverity.Info,
|
||||
},
|
||||
Type = type,
|
||||
});
|
||||
health.HealthStatus = healthStatus.GetString();
|
||||
}
|
||||
else if (healthStatus.TryGetProperty("status", out var inner) &&
|
||||
inner.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
health.HealthStatus = inner.GetString();
|
||||
}
|
||||
if (healthStatus.TryGetProperty("configurationIssues", out var nested))
|
||||
issueElements.AddRange(nested.EnumerateArray());
|
||||
}
|
||||
if (status.TryGetProperty("configurationIssues", out var flat))
|
||||
issueElements.AddRange(flat.EnumerateArray());
|
||||
foreach (var issue in issueElements)
|
||||
{
|
||||
var severity = issue.TryGetProperty("severity", out var sev) && sev.ValueKind == JsonValueKind.String
|
||||
? sev.GetString()
|
||||
: null;
|
||||
var type = issue.TryGetProperty("type", out var t) && t.ValueKind == JsonValueKind.String
|
||||
? t.GetString()
|
||||
: null;
|
||||
health.ConfigurationIssues.Add(new StreamConfigurationIssue
|
||||
{
|
||||
Severity = severity switch
|
||||
{
|
||||
"error" => StreamIssueSeverity.Error,
|
||||
"warning" => StreamIssueSeverity.Warning,
|
||||
_ => StreamIssueSeverity.Info,
|
||||
},
|
||||
Type = type,
|
||||
});
|
||||
}
|
||||
return health;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user