Improve YouTube WebSub handling and live-status diagnostics - #1685
Conversation
Add safe operation-specific diagnostics, discovery observations, and fallback regression coverage without changing polling or retry policies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Provider responses can still be buffered without an effective size limit, and the final review comments remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Improves YouTube WebSub callback handling, diagnostics, quota documentation, and related test coverage.
Changes:
- Adds secret-safe, operation-specific diagnostics and Retry-After metadata.
- Handles invalid notifications and denial reports without mutating live state.
- Expands callback, discovery, and diagnostic tests and documentation.
File summaries
| File | Reviewed changes |
|---|---|
tests/StaticHost.Tests/Live/YouTubeWebSubServiceTests.cs |
Updates WebSub logging and discovery assertions. |
tests/StaticHost.Tests/Live/YouTubeDiagnosticsTests.cs |
Tests diagnostic classification, redaction, and recovery metadata. |
tests/StaticHost.Tests/Live/LiveEndpointsTests.cs |
Tests callback validation, denial handling, and confirmation behavior. |
tests/StaticHost.Tests/Live/InMemoryLiveStatusInfrastructure.cs |
Adds test state and confirmation observability helpers. |
src/statichost/StaticHost/Live/YouTube/YouTubeWebSubService.cs |
Adds discovery and subscription diagnostics. |
src/statichost/StaticHost/Live/YouTube/YouTubeLiveConfirmationQueue.cs |
Avoids duplicate failure logging. |
src/statichost/StaticHost/Live/YouTube/YouTubeDiagnostics.cs |
Implements safe provider failure classification and metadata handling. |
src/statichost/StaticHost/Live/YouTube/YouTubeClient.cs |
Captures provider failure diagnostics. |
src/statichost/StaticHost/Live/README.md |
Documents diagnostics, callbacks, and quota behavior. |
src/statichost/StaticHost/Live/LiveStatusOptions.cs |
Clarifies discovery quota behavior. |
src/statichost/StaticHost/Live/LiveEndpoints.cs |
Handles signatures, denials, and confirmation diagnostics. |
Review details
Suppressed comments (3)
src/statichost/StaticHost/Live/LiveEndpoints.cs:354
- This message is also emitted for a retry of a previously accepted verification:
TryConfirmreturnstruefromRecentConfirmationwithout changingRenewAtor clearingRetry. Saying every accepted callback establishes/renews the lease and resets backoff makes the diagnostic claim a state mutation that did not occur; distinguish the initial confirmation from a retry (or log the transition result).
logger.LogInformation(
"YouTube {Operation} verified; granted lease {LeaseSeconds}s. Verification establishes or renews the lease and resets subscription backoff.",
"WebSubVerification", leaseSeconds);
src/statichost/StaticHost/Live/YouTube/YouTubeDiagnostics.cs:37
- This read is capped at 4,097 characters, but all current YouTube callers invoke
GetAsync/PostAsyncwith the defaultResponseContentRead, soHttpClienthas already buffered the entire provider response before this method runs. A large error page can therefore still cause unbounded response buffering despite the documented 4,096-character diagnostic limit; useResponseHeadersReadplus a bounded response-body read (or an equivalent size limit) at the request call sites.
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var buffer = new char[BodyLimit + 1];
var count = await reader.ReadBlockAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false);
details = DescribeBody(new string(buffer, 0, Math.Min(count, BodyLimit)), count > BodyLimit, secrets);
src/statichost/StaticHost/Live/YouTube/YouTubeWebSubService.cs:175
- The HTTP 2xx acceptance is logged only after
MarkRequestSentAsynccompletes. If the hub accepts the POST but the Redis/state write fails, this log is skipped and the outer handler reports only aBackgroundTickfailure, so operators cannot distinguish provider acceptance from local persistence failure. Record the acceptance before (or in a finally around) the state write, while keeping the message explicit that it is not verification.
await _subscriptions.MarkRequestSentAsync(
request,
_time.GetUtcNow(),
cancellationToken).ConfigureAwait(false);
logger.LogInformation(
- Files reviewed: 11/11 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Frontend HTML artifact readyThe latest frontend build uploaded the This comment updates automatically when a new frontend build artifact is uploaded. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Summary
hub.mode=deniedas an untrusted report without mutating subscription or live state.Validation
Live investigation and limitations
Production telemetry and a standalone local manual probe reproduced HTTP 503 from Google's subscription hub after approximately 20 seconds, with
Retry-After: 120andTransient error; please try again later. The public tunnel reached the local listener, but no hub verification or denial callback was observed. Controlled async/sync and HTTPS/HTTP-topic comparisons all reproduced the failure.This PR fixes confirmed callback-handling and diagnostic gaps; it does not claim to resolve Google's subscription failure or prove end-to-end livestream detection. Production retains the documented HTTPS topic, asynchronous verification, existing polling intervals, and retry policy. A shared daily API quota limiter is not implemented, and production project's actual quota allocation remains unconfirmed. The standalone manual probe is outside this repository and is not included in this PR.