diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs
index b4c133f53..43cdb7d0b 100644
--- a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs
+++ b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs
@@ -673,12 +673,16 @@ public class HlsSessionWorker : IHlsSessionWorker
}
}
}
- catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
+ catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException
+ && cancellationToken.IsCancellationRequested)
{
// a cancellation anywhere in this method (including inside the mediator sends, which sit
// outside the inner ffmpeg try below) is a shutdown or a client disconnect, not a fault.
// Without this it reaches the catch-all and logs a channel-level ERROR with a stack
- // trace on every graceful teardown. (ersatztv#473 review)
+ // trace on every graceful teardown. The token check is load-bearing: TaskCanceledException
+ // is also what HttpClient throws on ITS OWN timeout, and a real timeout inside ffprobe, a
+ // media-server call or subtitle extraction must keep its ERROR-level signal rather than
+ // being downgraded to a routine teardown. (ersatztv#473 review)
_logger.LogInformation("Terminating HLS session for channel {Channel}", _channelNumber);
return false;
diff --git a/ErsatzTV.Core/Interfaces/Streaming/IRemoteStreamProber.cs b/ErsatzTV.Core/Interfaces/Streaming/IRemoteStreamProber.cs
index c3b20e434..a936fd9d6 100644
--- a/ErsatzTV.Core/Interfaces/Streaming/IRemoteStreamProber.cs
+++ b/ErsatzTV.Core/Interfaces/Streaming/IRemoteStreamProber.cs
@@ -18,10 +18,11 @@ public interface IRemoteStreamProber
/// true, so a probe that cannot answer never prevents a tune that would have worked.
///
///
- /// Thrown when is cancelled. Caller cancellation is a
- /// genuine signal (shutdown / client disconnect), not a probe failure, so it propagates
- /// rather than being absorbed by the fail-open behaviour above. The prober's own internal
- /// timeout does not throw — it fails open.
+ /// May propagate when is cancelled while the probe is
+ /// in flight. Caller cancellation is a genuine signal (shutdown / client disconnect), not a
+ /// probe failure, so it is not absorbed by the fail-open behaviour above. Cancelling after
+ /// the probe has already completed returns normally. The prober's own internal timeout does
+ /// not throw — it fails open.
///
Task IsAvailable(string url, CancellationToken cancellationToken);
}
diff --git a/docs/handoffs/chicorytv-issue-queue.md b/docs/handoffs/chicorytv-issue-queue.md
index 0355ff869..77cab0503 100644
--- a/docs/handoffs/chicorytv-issue-queue.md
+++ b/docs/handoffs/chicorytv-issue-queue.md
@@ -249,6 +249,18 @@ HARD CONSTRAINTS:
must supersede a live run, SAY SO explicitly instead of leaving it burning. (`dispatch_workflow` is
a different route and still works for re-triggering a **main** run.) Corrects the older "superseded
runs drain on their own" note below: they do finish, but they hold a slot while doing it.
+- **A lone `decisions.md append-only` red is a KNOWN INFRA FLAKE — do NOTHING** (operator-stated,
+ 2026-07-19). When it is the **only** red: do not investigate it, and do **not** create a new run or
+ commit to clear it — no rebase, no `--amend`, no no-op push. **The operator reruns that single job
+ from the Gitea UI.** Report it as a known flake and carry on; only if *other* jobs are red too does
+ the run deserve diagnosis. Same family as the killed-job rule below (a spurious single-job red is
+ cleared by the operator's selective rerun, never by pushing — and pushes can't be cancelled, see the
+ batching constraint above). **The trap is that a convincing local explanation is always available.**
+ On #473/PR #479 the job went red just after `main` landed its own `decisions.md` entry, so "mine is
+ no longer at EOF, I must rebase" looked airtight — I rebased, and it went red **again** on a head
+ whose diff was a verified pure EOF append with zero deleted lines. A rebase that provably satisfied
+ the gate's stated rule did not turn it green, which is the proof the red was never about the diff.
+ **Check this rule before theorising about the cause.**
- **Do NOT gate or throttle your push on host health — trust the Gitea build queue.** The runners were
retuned for stability (operator, 2026-07-17); queueing is the queue's job, not yours. Don't SSH to
bumblebee to sample load/RAM before pushing, and don't hand-schedule around other sessions' runs. Batch