Skip to content

Pick the camera player from the camera's reported capabilities - #5686

Draft
bgoncal wants to merge 27 commits into
mainfrom
camera-player-capabilities
Draft

Pick the camera player from the camera's reported capabilities#5686
bgoncal wants to merge 27 commits into
mainfrom
camera-player-capabilities

Conversation

@bgoncal

@bgoncal bgoncal commented Sep 7, 2026

Copy link
Copy Markdown
Member

AI Policy

Select exactly one option that describes AI usage in this contribution:

  • I have not used AI for this contribution.
  • AI assistance was used for this contribution.
  • AI fully generated the code for this contribution, but I've reviewed and understood it before submitting and will respond without AI during review.

Summary

The camera player could not play every camera the webview can. It opened a WebRTC offer on every camera and only learned the right stream type by failing its way down a cascade, and the HLS step of that cascade never worked, so cameras WebRTC could not carry ended up on the still-image proxy.

It now asks camera/capabilities for frontend_stream_types and builds the fallback order from it, the same way the frontend's ha-camera-stream does:

  • no stream support → MJPEG
  • hls only → HLS, then MJPEG
  • web_rtc only (Nest and friends, which have no HLS) → WebRTC, then MJPEG
  • both (go2rtc) → WebRTC, then HLS, then MJPEG

The HLS player read its stream request before the promise resolved, so it always threw "no stream available". It now awaits the request, and joins the playlist path without a leading slash so servers on a subpath resolve.

The WebRTC path picks up the rest of the frontend player's behaviour: the offer carries the ICE candidates gathered so far, remote tracks are adopted from the peer connection instead of assumed to be on the transceiver the offer created, a failed ICE connection is rebuilt once before cascading, returning to the foreground restarts a stream that did not survive suspension, and an unsupported camera is recognised from the client-config rejection and the signaling error event as well as the offer rejection. getCandidatesUpfront is removed, since core no longer sends it.

On cellular the native player also gathered ICE candidates on every pdp_ip interface the phone has, because libwebrtc's iOS network monitor sits behind the WebRTC-Network-UseNWPathMonitor field trial and without it the network manager takes whatever getifaddrs lists. Only one of those interfaces can reach anything; the others produce host candidates that never connect, and the connectivity checks work through all of those pairs before reaching the relay pair that can — the seconds a 5G stream spent before connecting, when it connected at all. The frontend never had this problem because WKWebView only gathers on the default route. The trial is now enabled before the peer connection factory is created, so libwebrtc ignores interfaces outside the current network path and negotiates from the same interface set the webview does.

Smaller things the device logs turned up along the way: the picker fetched a still for every camera on each open and a camera the server cannot image failed each time, so snapshots are now fetched when the picker is shown and a failure is remembered for five minutes; a snapshot response that is not an image rejected nothing and left its promise pending; the model manager rebuilt its zone/person subscription on every reconnect because the auth handshake's version update counts as a server change, which cost a duplicate subscribe_entities and an unsubscribe per reconnect; and the peer connection now bundles audio and video from the start (maxBundle) so it gathers one candidate set instead of one per m-line.

Screenshots

No visual change — same player UI, chosen differently.

Link to pull request in Documentation repository

Documentation: not needed, no user-facing behaviour is added.

Any other notes

One gap this cannot close: the bundled WebRTC build ships no HEVC decoder for Apple platforms, while WKWebView gets H.265 from WebKit. An H.265 camera still plays in the webview and not in the native player — but it now times out and falls to HLS, where AVPlayer decodes it natively.


Generated by Claude Code

The in-app camera player opened a WebRTC offer on every camera and only
learned the right stream type by failing its way down a cascade, so cameras
the webview plays fine ended up on the still-image proxy.

Ask camera/capabilities for frontend_stream_types and build the fallback
order from it, the way ha-camera-stream does: no stream support goes straight
to MJPEG, an HLS-only camera never sends a doomed offer, a native-WebRTC
camera (Nest) knows it has no HLS behind it, and a go2rtc camera prefers
WebRTC with HLS as the fallback.

The HLS player also read its stream request before the promise resolved, so
it always threw "no stream available" and dropped to MJPEG. It now awaits the
request, and joins the playlist path without a leading slash so servers on a
subpath resolve.

The WebRTC path picks up the rest of the frontend player's behaviour: the
offer carries the ICE candidates gathered so far, remote tracks are adopted
from the peer connection instead of assumed, a failed ICE connection is
rebuilt once before cascading, returning to the foreground restarts a stream
that did not survive suspension, and an unsupported camera is recognised from
the client-config rejection and the signaling error event as well as the
offer rejection. getCandidatesUpfront is gone, since core no longer sends it.
Copilot AI lite review requested due to automatic review settings September 7, 2026 19:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core camera streaming selection and WebRTC/HLS connection behavior, and the remaining concurrency/thread-safety issues should be addressed before merge.

Pull request overview

This PR updates the native camera player to choose streaming methods based on the camera’s reported capabilities (camera/capabilities.frontend_stream_types), aligning the app’s behavior with the Home Assistant frontend’s ha-camera-stream selection logic and fixing HLS/WebRTC flow issues that previously caused unnecessary fallbacks to MJPEG.

Changes:

  • Introduces CameraCapabilities, CameraStreamType, and CameraStreamPlan to build an ordered stream fallback plan from frontend_stream_types.
  • Fixes HLS stream URL resolution by properly awaiting the stream request and correctly appending server-relative playlist paths (including subpath installs).
  • Improves WebRTC robustness (offer handling, track adoption, ICE failure retry, and lifecycle restart on foregrounding) and adds targeted unit tests for capability decoding and stream planning.
File summaries
File Description
Tests/App/Cameras/CameraStreamPlanTests.swift Adds tests validating player ordering derived from reported capabilities (and nil/unknown behavior).
Tests/App/Cameras/CameraCapabilitiesTests.swift Adds tests for decoding camera/capabilities responses and soft-failing fetch behavior.
Sources/App/Cameras/CameraPlayer/WebRTC/WebRTCViewPlayerViewModel.swift Refactors WebRTC setup to use callback-based send, improves unsupported detection, retries, and app lifecycle restart behavior.
Sources/App/Cameras/CameraPlayer/WebRTC/WebRTCVideoPlayerView.swift Hooks into scenePhase to suspend/resume WebRTC intent and restart dead streams.
Sources/App/Cameras/CameraPlayer/WebRTC/WebRTCClientConfiguration.swift Removes getCandidatesUpfront handling and adds data-channel label support.
Sources/App/Cameras/CameraPlayer/WebRTC/WebRTCClient.swift Updates offer semantics, adopts remote tracks dynamically, adds connection liveness helper, and handles receiving callbacks.
Sources/App/Cameras/CameraPlayer/CameraStreamType.swift Adds stream-type enum mirroring core/frontend frontend_stream_types.
Sources/App/Cameras/CameraPlayer/CameraStreamPlan.swift Adds stream-plan builder that selects preferred fallback order ending in MJPEG.
Sources/App/Cameras/CameraPlayer/CameraStreamHLSView.swift Fixes HLS stream request timing and baseURL/subpath-safe playlist URL construction.
Sources/App/Cameras/CameraPlayer/CameraPlayerView.swift Switches from a fixed WebRTC-first cascade to a capabilities-driven player list with guarded fallback advancement.
Sources/App/Cameras/CameraPlayer/CameraPlayerType.swift Adds internal player-type enum used by the capabilities-driven player selection.
Sources/App/Cameras/CameraPlayer/CameraCapabilities.swift Adds capability model + websocket fetch for camera/capabilities.
HomeAssistant.xcodeproj/project.pbxproj Adds a synchronized “Cameras” group to the Xcode project structure.
Review details

Suppressed comments (2)

Sources/App/Cameras/CameraPlayer/CameraPlayerView.swift:75

  • Launching an unscoped Task { ... } in .onAppear isn't automatically cancelled when the view disappears (e.g., dismiss) and can still update @State afterward. Prefer using SwiftUI's .task(id:) modifier so the work is cancelled with the view lifecycle and automatically reruns when cameraEntityId changes.

This issue also appears on line 337 of the same file.

        .onAppear {
            loadMetadata()
            loadCameras()
            Task { await loadCapabilities() }
        }
        .statusBarHidden(true)
        .persistentSystemOverlays(.hidden)
        .preferredColorScheme(.dark)
    }

Sources/App/Cameras/CameraPlayer/CameraPlayerView.swift:340

  • With .task(id: cameraEntityId) driving capability loading, this explicit Task { await loadCapabilities() } becomes redundant and can race with the .task-initiated fetch. Removing it avoids duplicate requests and keeps capability loading tied to the view lifecycle.
        cameraEntityId = entityId
        loadMetadata()
        Task { await loadCapabilities() }
    }
  • Files reviewed: 13/13 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/App/Cameras/CameraPlayer/WebRTC/WebRTCClient.swift
Comment thread Sources/App/Cameras/CameraPlayer/CameraStreamHLSView.swift Outdated
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Unused L10n strings detected

Found 2 unused localization strings in the codebase.

Click to see details
Parsing Strings.swift...
Found 2856 L10n strings

Reading all Swift source code...
Read 11702423 characters of Swift code

Checking for unused strings...
Checked 100/2856 strings...
Checked 200/2856 strings...
Checked 300/2856 strings...
Checked 400/2856 strings...
Checked 500/2856 strings...
Checked 600/2856 strings...
Checked 700/2856 strings...
Checked 800/2856 strings...
Checked 900/2856 strings...
Checked 1000/2856 strings...
Checked 1100/2856 strings...
Checked 1200/2856 strings...
Checked 1300/2856 strings...
Checked 1400/2856 strings...
Checked 1500/2856 strings...
Checked 1600/2856 strings...
Checked 1700/2856 strings...
Checked 1800/2856 strings...
Checked 1900/2856 strings...
Checked 2000/2856 strings...
Checked 2100/2856 strings...
Checked 2200/2856 strings...
Checked 2300/2856 strings...
Checked 2400/2856 strings...
Checked 2500/2856 strings...
Checked 2600/2856 strings...
Checked 2700/2856 strings...
Checked 2800/2856 strings...

================================================================================
UNUSED STRINGS REPORT
================================================================================

Found 2 unused strings:


APPINTENTS:
  - L10n.AppIntents.ActiveEntities.Filter.climates
    Key: app_intents.active_entities.filter.climates
    Line: 297
  - L10n.AppIntents.ActiveEntities.Filter.locks
    Key: app_intents.active_entities.filter.locks
    Line: 305

================================================================================
Total unused: 2
================================================================================

================================================================================
Copy-paste these keys into the "Lokalise: Delete Keys" workflow (keys input):
================================================================================
app_intents.active_entities.filter.climates,app_intents.active_entities.filter.locks

To remove them, run the
Lokalise: Delete Keys
workflow — it deletes the keys from Lokalise and opens a PR removing them from
Localizable.strings and regenerating Strings.swift. Copy-paste these keys into the keys input:

app_intents.active_entities.filter.climates,app_intents.active_entities.filter.locks

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.74871% with 199 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.25%. Comparing base (d99a0d6) to head (5add746).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...es/App/Cameras/CameraPlayer/CameraPlayerView.swift 1.16% 85 Missing ⚠️
...App/Cameras/CameraPlayer/WebRTC/WebRTCClient.swift 0.00% 71 Missing ⚠️
...pp/Cameras/CameraPlayer/CameraHLSAssetLoader.swift 50.00% 13 Missing ⚠️
...App/Cameras/CameraPlayer/CameraStreamHLSView.swift 20.00% 12 Missing ⚠️
...as/CameraPlayer/WebRTC/WebRTCVideoPlayerView.swift 0.00% 11 Missing ⚠️
...ameraPlayer/WebRTC/WebRTCViewPlayerViewModel.swift 98.42% 3 Missing ⚠️
...s/CameraPlayer/WebRTC/WebRTCFakeStreamClient.swift 95.00% 2 Missing ⚠️
Sources/Shared/API/HAAPI.swift 0.00% 2 Missing ⚠️

❌ Your patch check has failed because the patch coverage (65.74%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5686      +/-   ##
==========================================
+ Coverage   41.59%   42.25%   +0.65%     
==========================================
  Files        1108     1117       +9     
  Lines       77099    77825     +726     
==========================================
+ Hits        32073    32887     +814     
+ Misses      45026    44938      -88     
Files with missing lines Coverage Δ
.../App/Cameras/CameraPlayer/CameraCapabilities.swift 100.00% <100.00%> (ø)
...meras/CameraPlayer/CameraPickerSnapshotCache.swift 100.00% <100.00%> (ø)
...pp/Cameras/CameraPlayer/CameraPlayerPlayback.swift 100.00% <100.00%> (ø)
...es/App/Cameras/CameraPlayer/CameraStreamPlan.swift 100.00% <100.00%> (ø)
...ameraPlayer/WebRTC/WebRTCClientConfiguration.swift 100.00% <100.00%> (+100.00%) ⬆️
...ameras/CameraPlayer/WebRTC/WebRTCFieldTrials.swift 100.00% <100.00%> (ø)
...meraPlayer/WebRTC/WebRTCServerConnectionGate.swift 100.00% <100.00%> (ø)
...layer/WebRTC/WebRTCServerConnectionReadiness.swift 100.00% <100.00%> (ø)
Sources/Shared/API/Models/LegacyModelManager.swift 73.36% <100.00%> (+2.74%) ⬆️
...s/CameraPlayer/WebRTC/WebRTCFakeStreamClient.swift 95.00% <95.00%> (ø)
... and 7 more

... and 44 files with indirect coverage changes

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 35.13% of the lines this pull request
adds or changes (98 of 279 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

@home-assistant
home-assistant Bot marked this pull request as draft September 7, 2026 21:14
@home-assistant

home-assistant Bot commented Sep 7, 2026

Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

claude and others added 6 commits September 7, 2026 21:17
A stream that dropped to ICE disconnected was left frozen: only failed was
handled, and moving between cellular and Wi-Fi lands on disconnected and never
recovers on its own. Device logs show a camera connecting over 5G in about
nine seconds, playing for twenty-odd, then sitting disconnected until the
player was closed and reopened.

Give a dropped connection a few seconds to mend itself — WebRTC re-checks its
candidates and often does — and rebuild the stream when it does not. Reaching
a connected state again resets the attempts, so an interruption it recovered
from does not count against a later one.

The rebuild also re-arms the connection timeout, which the first rendered
frame had cancelled; without it a retry that never connects leaves the loader
spinning with nothing to cascade it onwards. The timeout itself goes to 25s,
since gathering and checking relay candidates over cellular takes the better
part of ten seconds on a healthy link.
On cellular the peer connection gathered on every interface the phone has —
several private pdp_ip addresses, the 464XLAT address, link-local and
unique-local IPv6, each as UDP and TCP — and offered close to fifty candidates
for a camera reachable over one of them. Connectivity checks then worked
through the unreachable pairs first: device logs show nine seconds of that
before the relay pair won, on top of the round trip for the answer.

Drop the ones that could never carry the stream (TCP host candidates on
private addresses, link-local interfaces), prune redundant TURN ports, and
pre-gather one candidate so the offer carries it instead of the backend
waiting on the first trickled one. TURN over TCP is unaffected — it comes from
the ICE server list, not from host candidate gathering.
Twelve releases on from the pinned 140, tracking the same upstream libwebrtc
the frontend's browser is built from.
WebRTCClientTests hung the unit test bundle: the suite started and produced no
result before the job hit its sixty minute timeout, twice. Constructing an
RTCPeerConnection opens sockets and starts gathering, which does not complete
on a CI runner, and nothing else in the suite had ever done it.

Keep the tests that drive the view model through its mock connection and drop
the ones that stand up real WebRTC objects. The peer connection wrapper goes
back to being covered by the app rather than by tests.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 37.25% of the lines this pull request
adds or changes (130 of 349 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

Pruning redundant TURN ports cut the relay candidates offered on cellular from
three to one. Behind carrier-grade NAT that is the only kind of candidate that
can carry the stream — the host addresses are private and the reflexive one is
not reachable inbound — so each relay candidate is a separate chance for the
stream to come up rather than redundancy worth removing. Device logs show two
consecutive attempts with an identical single-relay candidate set, one
connecting in nine seconds and the other never connecting at all.

Dropping TCP host candidates and link-local interfaces stays: those can never
carry the stream, and they are what the candidate set needed trimming of.
AVFoundation does its own networking outside URLSession delegates, so it
never presents the client certificate or answers the trust challenge a
security exception covers. On a server using either, the HLS fallback
could not load at all. Route the asset's requests through the app's
session, the same way the notification extension's player already does.
WebRTC signaling runs over the server's WebSocket. When the network
changes under it, that socket is dead but HAKit needs the better part of
a minute to notice, and commands sent meanwhile get no reply and no
error. The player rebuilt its stream five seconds after ICE dropped,
sent camera/webrtc/get_client_config into that socket, and then sat
until its own 25s timeout gave up on WebRTC entirely.

Hold the attempt until the connection can carry it, reconnecting an idle
socket rather than waiting on one nothing will bring back, and start the
stream's timeout when the server is reachable instead of at open. A
stream now survives moving between Wi-Fi and cellular. The wait is
bounded so an unreachable server still reports a failure.
@github-actions
github-actions Bot dismissed stale reviews from themself September 8, 2026 07:40

Patch coverage could not be measured on this run, so the gate no longer applies.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 47.64% of the lines this pull request
adds or changes (242 of 508 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

A socket whose network was taken away still reports itself ready: nothing
has tried to use it since, and HAKit needs some forty-five seconds to
find out. Device logs caught the race exactly — Wi-Fi went five
milliseconds after the connection reported ready, and the rebuilt
stream's get_client_config went out over it and was never answered, where
the same command takes 43ms on a working connection.

Watch a signaling command for that silence and start over when it
happens, waiting for a connection that has actually been re-established
rather than believing the stale one. Cascading instead only hands the
next player the same dead socket.
@github-actions
github-actions Bot dismissed their stale review September 8, 2026 09:16

Patch coverage could not be measured on this run, so the gate no longer applies.

Every other test that builds an API swaps in a mock connection. These
did not, so the API kept its real one and went looking for a server that
is not there, leaving retries running behind the tests that follow.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 51.08% of the lines this pull request
adds or changes (283 of 554 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

… uses

On cellular the peer connection still gathered on every pdp_ip interface the
phone has, because libwebrtc's iOS network monitor is behind the
WebRTC-Network-UseNWPathMonitor field trial and without it the network manager
takes whatever getifaddrs lists. Only one of those interfaces can reach
anything; the rest produce host candidates that never connect, and the
connectivity checks work through every one of those pairs before they reach
the relay pair that can. That is the stretch of seconds a 5G stream spent
before connecting, when it connected at all, while the frontend in the
webview connected at once: WKWebView only gathers on the default route.

Turn the trial on before the peer connection factory is created. With the
path monitor installed, libwebrtc reports interfaces outside the current
network path as unavailable and ignores them, leaving the same interface set
the webview's player negotiates from.

Also record the WebRTC 152.0.0 pin in Package.resolved, which the project file
already required.
@github-actions
github-actions Bot dismissed their stale review September 8, 2026 11:28

Patch coverage could not be measured on this run, so the gate no longer applies.

…scription per reconnect

The camera picker fetched a still for every camera the moment the player
opened, and a camera the server cannot image answered each of those with a
500 on every open. Snapshots are now fetched when the picker menu is shown,
kept in a process-wide cache, and a failure is remembered for five minutes so
the same camera is not asked again on the next open. A snapshot response that
is not an image now rejects instead of leaving the promise pending forever.

Every reconnect made the model manager tear down and rebuild its zone and
person subscription, because HAKit reports the server changed when the auth
handshake updates its version. Device logs show the resulting pair of
subscribe_entities plus an unsubscribe of the first on each reconnect, and
HAKit complaining about the result and event arriving for the request it had
already dropped. The subscription is only rebuilt when the set of subscribable
servers changes.

Bundle audio and video onto one transport from the start (max-bundle), so the
peer connection gathers and checks one candidate set instead of one per
m-line; every WebRTC answerer bundles, so nothing is lost.
@bgoncal
bgoncal marked this pull request as ready for review September 8, 2026 21:08
@bgoncal
bgoncal enabled auto-merge (squash) September 8, 2026 21:08

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 49.61% of the lines this pull request
adds or changes (315 of 635 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

@home-assistant
home-assistant Bot marked this pull request as draft September 8, 2026 22:04
auto-merge was automatically disabled September 8, 2026 22:04

Pull request was converted to draft

The paths after the client configuration — sending the offer, flushing
candidates once the session arrives, applying the answer and remote
candidates, the error event, rebuilding on a failed or stalled connection, the
first-frame and connection-wait timeouts, and restarting after a dead
background — only ran against a real RTCPeerConnection, which the test bundle
cannot stand up on CI. The view model now takes the client through a small
protocol and its timings through a struct, with the production values as the
defaults, and a debug-only fake client drives those paths in tests.

Also covers unsubscribe on the model manager.
@github-actions
github-actions Bot dismissed their stale review September 8, 2026 23:05

Patch coverage could not be measured on this run, so the gate no longer applies.

A PR run that misses its DerivedData key restores the newest cache main warmed,
and with it the Clang modules precompiled for main's binary dependencies. This
PR moves the WebRTC pin, so the checked-out headers are newer than the module
built from the old ones, and Xcode fails the build with "has been modified
since the module file was built" rather than rebuilding it. Drop the explicit
precompiled modules after a fallback restore so they are built against the
headers actually present.
HAKit posts state transitions asynchronously and without the state they
announce, so a socket that drops and is back within one turn of the main queue
delivers two notifications that both read ready on arrival. The gate waited
for a reading that was not ready before trusting ready again, and so never
opened. It was armed while the state read ready, which makes any transition
since the evidence it needs.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 66.33% of the lines this pull request
adds or changes (461 of 695 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

@github-actions
github-actions Bot dismissed their stale review September 9, 2026 00:52

Patch coverage could not be measured on this run, so the gate no longer applies.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of this pull request is below 90%

The unit tests run 67.05% of the lines this pull request
adds or changes (466 of 695 coverable lines).

The per-file breakdown, and the changed lines no test runs, are in the
job summary. Adding tests for those lines and pushing dismisses this
review automatically.

Lines that carry no executable code, and files the unit test targets do not
build, are not counted. If the new code genuinely cannot be unit tested, a
maintainer can dismiss this review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants