# TASK 44 — TEST-tab chat fix: resolve the liveChatId (snippet + live-timing) > Catalog: [`TASKS.md`](../TASKS.md). Status: ✅ **SHIPPED 2026-09-25** — the open > creator report from HANDOFF ("I still cannot post a chat message in the TEST tab") > diagnosed and fixed as its own scoped unit. ## Provenance - **2026-09-24, HANDOFF** filed the creator report as open: "I still cannot post a chat message in the TEST tab." The TEST drawer's Mock Chat Input posts a real `liveChat/messages.insert`; the drawer only enables it when a liveChatId is resolved. - **2026-09-25 diagnosis:** the reported feedback line "Chat polling couldn't start — Mock Chat Input is disabled" is `TestSessionViewModel.BeginTest` running with a **null liveChatId**. That id comes from `YouTubeStreamService.GetBroadcastLiveChatIdAsync`, and two API facts (from the official liveBroadcasts reference + the `GetLiveChatId.java` sample) pin why it never resolved: 1. **It lives under `snippet.liveChatId`** — `contentDetails` has no such property at all. The code requested `part=contentDetails` and read `contentDetails.liveChatId`, a path that can NEVER resolve. 2. **YouTube only populates it once the broadcast is `live`** (the official sample lists `broadcastStatus=active`). The fetch ran right after broadcast insert (lifecycleStatus `ready`) AND before `_framePump.StartAsync()` pushed the RTMP stream that `enableAutoStart` needs to flip the broadcast to live — so even the correct property would have been empty at fetch time. ## What shipped - `Services/YouTubeStreamService.cs`: - `GetBroadcastLiveChatIdAsync(broadcastId, maxAttempts = 10, delayMs = 2000)` now requests **`part=snippet`** and reads **`snippet.liveChatId`**, and **polls with a bounded retry** until the id appears or the attempts exhaust (never throws). The default retry (~20s) gives the encoder time to connect and the broadcast to go live. - New private `FetchLiveChatIdAsync` holds the single read; the public method owns the retry loop. - `ViewModels/MainViewModel.Streaming.Operations.cs`: - `PrepareAndStartLiveAsync` **starts the frame pump FIRST**, then resolves the liveChatId — the reorder is required for the id to ever exist. Chat remains non-fatal: failure logs and the stream continues. - TEST drawer wiring preserved: `TestSession.BeginTest(broadcastId, liveChatId)` still opens the dock once the id resolves → Mock Chat Input becomes sendable. - Docs: `ai.md` (Test Stream section), `TASKS.md` (row 44), `HANDOFF.md` rewritten. ## Validation (Good Dog: ONE integration test) `ytLive.Tests/YouTubeStreamServiceTests.cs`: `GetBroadcastLiveChatIdAsync_Polls_Snippet_Until_Id_Appears` — a fake broadcast that starts WITHOUT a liveChatId (not yet live) and gains it on a later poll; asserts the retry eventually resolves `Cg0LIVECHAT42`, that it polled ≥3 times, and that every request asked for **snippet** (never contentDetails) — the exact seam that was broken. **Gate: clean build 0 warnings; full vstest 318/318 passed.** ## Reference - https://developers.google.com/youtube/v3/live/docs/liveBroadcasts (snippet.liveChatId; contentDetails has no liveChatId) - https://github.com/youtube/api-samples/blob/master/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java (sample lists broadcastStatus=active — id only present on live broadcasts)