Finding
tokio::spawn returns a JoinHandle<Result<(), RenderError>> for both audio_task and status_task. The select! arms pattern-match the JoinHandle output with if let Err(e) = result, which matches only JoinError (a task panic). When receive_audio or send_status_reports returns a normal Err(RenderError), the handle resolves to Ok(Err(RenderError)). The outer if let Err(e) does not match, so the RenderError is dropped: no log line is emitted and connect_and_run falls through to Ok(()).
Evidence
crates/archon/src/render/runner.rs:258-262 — audio arm matches only the panic case; the warn text confirms intent:
result = audio_task => {
if let Err(e) = result {
warn!(error = %e, "audio task panicked");
}
}
crates/archon/src/render/runner.rs:263-267 — identical pattern for the status task:
result = status_task => {
if let Err(e) = result {
warn!(error = %e, "status task panicked");
}
}
The Ok(Err(RenderError)) arm has no handler in either branch, and the function returns Ok(()) at crates/archon/src/render/runner.rs:271.
Why this matters
A real audio decode error, a QUIC stream error, or a DSP failure inside receive_audio (and any failure inside send_status_reports) produces no log line and does not surface as an error from connect_and_run. The caller in run_renderer_loop cannot distinguish a clean connection close from a faulted task, so the exponential-backoff reconnect path is never taken — the renderer goes dark silently. On a counter-surveillance device this destroys observability into the audio path: an operator has no signal that the renderer failed (or was forced to fail by an adversary tampering with the stream), and the channel will not self-heal via reconnect.
Desired correction
Unwrap the inner result explicitly in both arms, e.g.:
result = audio_task => match result {
Ok(Ok(())) => {}
Ok(Err(e)) => { warn!(error = %e, "audio task failed"); /* propagate to trigger reconnect */ }
Err(e) => warn!(error = %e, "audio task panicked"),
}
Apply the same three-way match to status_task, and propagate the inner Err out of connect_and_run so the reconnect loop engages.
Done when: a simulated audio decode error in receive_audio produces a warn! log distinct from the panic message and causes connect_and_run to return Err, exercising the reconnect-backoff path in run_renderer_loop.
Finding
tokio::spawnreturns aJoinHandle<Result<(), RenderError>>for bothaudio_taskandstatus_task. Theselect!arms pattern-match theJoinHandleoutput withif let Err(e) = result, which matches onlyJoinError(a task panic). Whenreceive_audioorsend_status_reportsreturns a normalErr(RenderError), the handle resolves toOk(Err(RenderError)). The outerif let Err(e)does not match, so theRenderErroris dropped: no log line is emitted andconnect_and_runfalls through toOk(()).Evidence
crates/archon/src/render/runner.rs:258-262— audio arm matches only the panic case; the warn text confirms intent:crates/archon/src/render/runner.rs:263-267— identical pattern for the status task:The
Ok(Err(RenderError))arm has no handler in either branch, and the function returnsOk(())atcrates/archon/src/render/runner.rs:271.Why this matters
A real audio decode error, a QUIC stream error, or a DSP failure inside
receive_audio(and any failure insidesend_status_reports) produces no log line and does not surface as an error fromconnect_and_run. The caller inrun_renderer_loopcannot distinguish a clean connection close from a faulted task, so the exponential-backoff reconnect path is never taken — the renderer goes dark silently. On a counter-surveillance device this destroys observability into the audio path: an operator has no signal that the renderer failed (or was forced to fail by an adversary tampering with the stream), and the channel will not self-heal via reconnect.Desired correction
Unwrap the inner result explicitly in both arms, e.g.:
Apply the same three-way match to
status_task, and propagate the innerErrout ofconnect_and_runso the reconnect loop engages.Done when: a simulated audio decode error in
receive_audioproduces awarn!log distinct from the panic message and causesconnect_and_runto returnErr, exercising the reconnect-backoff path inrun_renderer_loop.