Skip to content

fix(voip): prevent stale audio-to-video upgrades - #1051

Merged
jlucaso1 merged 4 commits into
mainfrom
agent/fix-video-upgrade-state-machine
Jul 24, 2026
Merged

jlucaso1 merged 4 commits into
mainfrom
agent/fix-video-upgrade-state-machine

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

  • model self and peer video directions independently, including the complete native video-state set
  • serialize peer signaling, user actions, and the five-second upgrade timeout per call generation
  • bind accept_video to an exact peer request token so cancelled or superseded upgrades cannot attach camera endpoints
  • auto-accept video upgrades in the CLI even when the accepted call started as audio, while preserving the encoded-audio/Opus pipeline from feat(voip): add encoded audio pipeline and native Opus negotiation #1050

Root cause

Camera preparation waits for the first decodable IDR outside the signaling path. In the reported trace that preparation crossed the peer timeout, so the peer cancellation overtook the user action. The old code then completed the stale accept and retained /dev/video0, making the next upgrade fail with Device or resource busy.

The native APK/WASM behavior guards acceptance on the peer still being in Request state, serializes state transitions through a stream mutex, and expires local requests after five seconds. This change mirrors those invariants with a per-generation transition lock plus monotonic request epochs.

Verification

  • cargo fmt --all
  • cargo clippy --all --tests --features voip -- -D warnings
  • cargo test --workspace --exclude e2e-tests
  • cargo test --features voip voip::facade::tests -- --nocapture
  • cargo test --features voip handlers::call::tests -- --nocapture
  • cargo test -p wacore --features voip voip::registry -- --nocapture
  • cargo test -p whatsapp-rust-voip-cli

cargo test --all was also attempted; only the external E2E crate failed because its documented mock server was not running (Connection refused).

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 249f4632-4067-4348-b996-364d6027ebdd

📥 Commits

Reviewing files that changed from the base of the PR and between 714651b and 360b71a.

📒 Files selected for processing (1)
  • Cargo.toml

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added token-based mid-call video upgrade flow with explicit acceptor/initiator handling and support for automatic or manual acceptance.
    • Extended video state coverage (including paused, error, timeout/cancel-by-timeout, and unknown-peer) and updated event signaling to optionally include the upgrade token.
  • Bug Fixes

    • Prevented cancelled or superseded video upgrade requests from being applied, with stricter “current transition” gating for concurrent upgrades.
    • Improved endpoint release/teardown and cleanup across reject/disable/terminal/timeout/expiry scenarios.
  • Documentation

    • Updated <call><video> signaling docs to reflect the correct codec/accept attribute values.

Walkthrough

VoIP video upgrades now use generation-scoped transition state and tokens. Typed video events expose tokens, acceptance validates request freshness, timeout and teardown paths release endpoints, and the CLI supports token-based manual or automatic peer acceptance.

Changes

VoIP video upgrade lifecycle

Layer / File(s) Summary
Video state and token contracts
wacore/src/types/call.rs, wacore/src/voip/*, src/voip/mod.rs, src/client/voip.rs, wacore/src/stanza/call.rs, Cargo.toml
Video states, upgrade tokens, event payloads, re-exports, runtime errors, stanza documentation, and Tokio test features are updated.
Generation-scoped registry state
wacore/src/voip/registry.rs
CallRegistry tracks per-generation locks, video directions, request epochs, token validity, cancellation, and downgrade state.
Inbound video transition handling
src/handlers/call.rs
Typed video acknowledgements are serialized, classified, committed, and emitted as token-bearing events with updated teardown and orientation behavior.
Token-bound upgrade orchestration
src/voip/facade.rs
Initiator and acceptor flows validate tokens, coordinate endpoint teardown, perform role-specific handshakes, and clean up failed or timed-out upgrades.
CLI video upgrade interaction
examples/voip-cli/src/main.rs
The CLI stores peer request tokens, separates initial video from auto-accept behavior, and uses tokens for manual or automatic acceptance.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Peer
  participant CallHandler
  participant CallRegistry
  participant CallHandle
  participant VideoEndpoints
  Peer->>CallHandler: typed video upgrade request
  CallHandler->>CallRegistry: apply peer video state
  CallRegistry-->>CallHandler: current VideoUpgradeToken
  CallHandler-->>CallHandle: VideoStateChanged with token
  CallHandle->>CallRegistry: validate token
  CallHandle->>VideoEndpoints: attach source and sink
  CallHandle->>CallRegistry: complete peer request
Loading

Possibly related PRs

Suggested labels: api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: preventing stale audio-to-video upgrades in VoIP calls.
Description check ✅ Passed The description is directly about token-bound, generation-aware VoIP upgrade handling and matches the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/fix-video-upgrade-state-machine

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 9.57 MiB 9.58 MiB +9.12 KiB (+0.09%) 🔺
bin .text 7.65 MiB 7.66 MiB +8.50 KiB (+0.11%) 🔺
bin allocated (text+data+bss) 9.57 MiB 9.58 MiB +12.17 KiB (+0.12%) 🔺
llvm-lines wacore 472,529 472,569 +40 (+0.01%) 🔺
llvm-lines wacore copies 15,732 15,732 0
llvm-lines whatsapp-rust lib 661,857 661,857 0
llvm-lines whatsapp-rust lib copies 21,235 21,235 0
deps crates (Cargo.lock) 470 470 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.65 MiB 1.65 MiB -3.51 KiB (-0.21%) 🔽
.text wacore 597.36 KiB 597.05 KiB -312 B (-0.05%) 🔽
.text wacore_binary 86.27 KiB 86.27 KiB 0
.text wacore_libsignal 163.86 KiB 163.86 KiB 0
.text wacore_appstate 22.36 KiB 22.36 KiB 0
.text wacore_noise 22.46 KiB 22.46 KiB 0
.text waproto 1.74 MiB 1.74 MiB 0
.text whatsapp_rust_sqlite_storage 510.52 KiB 514.94 KiB +4.41 KiB (+0.86%) 🔺
.text whatsapp_rust_tokio_transport 39.84 KiB 40.03 KiB +191 B (+0.47%) 🔺
.text whatsapp_rust_ureq_http_client 10.28 KiB 10.33 KiB +46 B (+0.44%) 🔺
.text std 945.22 KiB 945.58 KiB +367 B (+0.04%) 🔺
.text other deps 1.87 MiB 1.88 MiB +7.33 KiB (+0.38%) 🔺
Top movers (cargo-bloat attribution)
Crate main PR Δ
whatsapp_rust_sqlite_storage 510.52 KiB 514.94 KiB +4.41 KiB (+0.86%)
metrics_exporter_prometheus (absent) 3.96 KiB +3.96 KiB
whatsapp_rust 1.65 MiB 1.65 MiB -3.51 KiB (-0.21%)
tokio 88.56 KiB 91.08 KiB +2.52 KiB (+2.85%)

Baseline: 123ec76e2 (latest main run) · Head: efd359a0c · Graphs

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes stale audio-to-video upgrade races by mirroring WA native invariants: each peer upgrade request is now identified by a monotonic VideoUpgradeToken epoch, and accept_video is bound to an exact token so a cancelled or superseded request cannot attach camera endpoints. Self and peer video directions are tracked independently via a new VideoNegotiation struct, and all transitions are serialised through a per-call async video_transition_lock.

  • Registry (registry.rs): adds VideoNegotiation (independent self/peer VideoState tracking), VideoUpgradeToken (generation + monotonic epoch), PeerVideoTransition (typed outcome), and per-call AsyncMutex to gate concurrent transitions.
  • Facade (facade.rs): accept_video now requires a VideoUpgradeToken; begin_video validates the token under the transition lock, a 5-second timeout task uses end_local_video_request with its epoch so a stale timer cannot affect a newer request or a completed upgrade.
  • Handler (call.rs): acquires current_video_transition before processing any <video> stanza; rejection/cancel/error states are applied unconditionally (even without a successful ack) to ensure local teardown is never skipped.

Confidence Score: 5/5

Safe to merge; the token-binding fix and transition lock correctly prevent the stale-accept race, all new code paths are covered by targeted unit tests, and the behavioral changes are well-isolated to video upgrade signaling.

The state machine logic is sound: epoch monotonicity prevents ABA token reuse, the transition lock serialises all concurrent paths (peer signaling, user actions, and the timeout task), and end_local_video_request failing silently when the epoch has already been consumed prevents double-teardown. The only observation is a convention gap in how VideoStateChanged fields are added inline rather than through a sealed payload struct, which does not affect runtime correctness.

wacore/src/voip/engine.rs — the VideoStateChanged variant payload should be extracted to a separate non_exhaustive + Builder struct to make future field additions non-breaking per the project convention.

Important Files Changed

Filename Overview
wacore/src/voip/registry.rs Core of the fix: adds VideoNegotiation (directional self/peer state), VideoUpgradeToken (monotonic epoch per peer request), PeerVideoTransition (typed outcome enum), and a per-call async transition lock. Logic for begin/end/complete local and peer requests is well-guarded and tested.
src/voip/facade.rs accept_video now requires a VideoUpgradeToken; begin_video acquires the transition lock and validates the token before attaching endpoints; the 5-second timeout task correctly uses end_local_video_request to prevent stale epoch from firing.
src/handlers/call.rs Handler now serialises all video transitions through the per-call transition lock fetched via current_video_transition; rejection/cancel states are processed unconditionally even when ack fails; new test covers the cancelled-token invalidation path.
wacore/src/types/call.rs Adds missing wire values (Paused=2, UpgradeRejectByTimeout=7, UpgradeCancelByTimeout=9, UnknownPeer=10, Error=20) and helper predicates is_inactive_for_call_mode / is_upgrade_request used throughout the state machine.
wacore/src/voip/engine.rs Adds upgrade_token to VideoStateChanged variant; CallEvent is already #[non_exhaustive] but inline struct fields still break consumers without .., requiring several pattern updates — the payload should be extracted to a non_exhaustive + Builder struct per convention.
examples/voip-cli/src/main.rs CLI correctly stores the token in VideoUi::PendingPeerRequest(token) and passes it through activate_peer_video_request; auto-accept now spawns a task that uses the token and checks it on completion.
wacore/src/voip/mod.rs Re-exports PeerVideoTransition and VideoUpgradeToken alongside CallRegistry.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App
    participant CallHandle
    participant Registry
    participant Signaling

    Note over App,Signaling: Accept path (peer requests video)
    Signaling->>Registry: current_video_transition(call_id)
    Registry-->>Signaling: (generation, transition_lock)
    Signaling->>Signaling: acquire transition_guard
    Signaling->>Registry: apply_peer_video_state(UpgradeRequestV2)
    Registry-->>Signaling: UpgradeRequested(token)
    Signaling->>App: "VideoStateChanged { upgrade_token: Some(token) }"
    Signaling->>Signaling: drop transition_guard
    App->>CallHandle: accept_video(token, src, sink)
    CallHandle->>Registry: video_transition_lock(call_id, gen)
    CallHandle->>CallHandle: acquire transition_guard
    CallHandle->>Registry: peer_video_request_is_current(token)
    Registry-->>CallHandle: true
    CallHandle->>CallHandle: attach_endpoints()
    CallHandle->>Signaling: send UpgradeAccept + Enabled
    CallHandle->>Registry: complete_peer_video_request(token)
    CallHandle->>CallHandle: drop transition_guard

    Note over App,Signaling: Initiate path (we request video)
    App->>CallHandle: start_video(src, sink)
    CallHandle->>Registry: video_transition_lock(call_id, gen)
    CallHandle->>CallHandle: acquire transition_guard
    CallHandle->>Registry: begin_local_video_request(call_id, gen) returns epoch
    CallHandle->>CallHandle: attach_endpoints + EnableAwaitingAccept
    CallHandle->>Signaling: send UpgradeRequestV2
    CallHandle->>CallHandle: drop transition_guard
    CallHandle->>CallHandle: spawn timeout(5s, epoch)

    alt Peer accepts
        Signaling->>Registry: apply_peer_video_state(UpgradeAccept) clears pending_self_request
        Note over CallHandle: timeout end_local_video_request fails (epoch cleared) no-op
    else Timeout fires
        CallHandle->>Registry: end_local_video_request(epoch) true
        CallHandle->>CallHandle: release_video_endpoints
        CallHandle->>Signaling: send UpgradeCancelByTimeout
    end

    Note over App,Signaling: Cancelled peer request
    Signaling->>Registry: apply_peer_video_state(UpgradeCancel) clears pending_peer_request
    App->>CallHandle: accept_video(stale_token, src, sink)
    CallHandle->>Registry: peer_video_request_is_current(stale_token) false
    CallHandle-->>App: Err(VideoUpgradeExpired)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App
    participant CallHandle
    participant Registry
    participant Signaling

    Note over App,Signaling: Accept path (peer requests video)
    Signaling->>Registry: current_video_transition(call_id)
    Registry-->>Signaling: (generation, transition_lock)
    Signaling->>Signaling: acquire transition_guard
    Signaling->>Registry: apply_peer_video_state(UpgradeRequestV2)
    Registry-->>Signaling: UpgradeRequested(token)
    Signaling->>App: "VideoStateChanged { upgrade_token: Some(token) }"
    Signaling->>Signaling: drop transition_guard
    App->>CallHandle: accept_video(token, src, sink)
    CallHandle->>Registry: video_transition_lock(call_id, gen)
    CallHandle->>CallHandle: acquire transition_guard
    CallHandle->>Registry: peer_video_request_is_current(token)
    Registry-->>CallHandle: true
    CallHandle->>CallHandle: attach_endpoints()
    CallHandle->>Signaling: send UpgradeAccept + Enabled
    CallHandle->>Registry: complete_peer_video_request(token)
    CallHandle->>CallHandle: drop transition_guard

    Note over App,Signaling: Initiate path (we request video)
    App->>CallHandle: start_video(src, sink)
    CallHandle->>Registry: video_transition_lock(call_id, gen)
    CallHandle->>CallHandle: acquire transition_guard
    CallHandle->>Registry: begin_local_video_request(call_id, gen) returns epoch
    CallHandle->>CallHandle: attach_endpoints + EnableAwaitingAccept
    CallHandle->>Signaling: send UpgradeRequestV2
    CallHandle->>CallHandle: drop transition_guard
    CallHandle->>CallHandle: spawn timeout(5s, epoch)

    alt Peer accepts
        Signaling->>Registry: apply_peer_video_state(UpgradeAccept) clears pending_self_request
        Note over CallHandle: timeout end_local_video_request fails (epoch cleared) no-op
    else Timeout fires
        CallHandle->>Registry: end_local_video_request(epoch) true
        CallHandle->>CallHandle: release_video_endpoints
        CallHandle->>Signaling: send UpgradeCancelByTimeout
    end

    Note over App,Signaling: Cancelled peer request
    Signaling->>Registry: apply_peer_video_state(UpgradeCancel) clears pending_peer_request
    App->>CallHandle: accept_video(stale_token, src, sink)
    CallHandle->>Registry: peer_video_request_is_current(stale_token) false
    CallHandle-->>App: Err(VideoUpgradeExpired)
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into agent/fix-video..." | Re-trigger Greptile

Comment thread wacore/src/voip/registry.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c48402585

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/voip/facade.rs Outdated
Comment thread wacore/src/voip/registry.rs
Comment thread wacore/src/voip/registry.rs
Comment thread wacore/src/voip/registry.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/handlers/call.rs (1)

223-387: 🩺 Stability & Availability | 🔵 Trivial

I went through this whole transition block against every test you added (cancelled_peer_request_invalidates_its_acceptance_token, video_transition_stays_serialized_through_enabled_announcement, refused_upgrade_tears_down_when_typed_ack_send_fails, committed_video_state_supersedes_diagnostics_when_event_queue_is_full, etc.) and it holds together. Teardown correctly still fires for cancel/reject/disable/error even when the typed ack send itself fails, and the event/dispatch gating correctly stays scoped to VideoState actions only. Solid work — this is what "ship it right the first time" looks like.

One thing worth being aware of operationally: _transition_guard is held across the client.send_node(...) awaits for the accept/enabled handshake stanzas, which means a peer's OWN incoming <video state> signal (or a local user action on the same call) is serialized behind those two outbound sends completing. That's clearly intentional — it mirrors WA's native stream mutex and is explicitly exercised by video_transition_stays_serialized_through_enabled_announcement — but on a slow/lossy connection this means video signaling on this call is briefly head-of-line-blocked behind the outbound stanza round-trip. Worth keeping in mind if upgrade latency ever gets flagged in the field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 223 - 387, The transition serialization in
the video-state handling block intentionally holds _transition_guard across the
awaited accept and enabled client.send_node calls. Preserve this locking scope
and ordering so peer signals and local actions remain serialized through the
handshake announcements; make no code change unless future requirements
explicitly address the resulting latency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/voip-cli/src/main.rs`:
- Around line 1424-1431: Update the auto_video branch in the call event listener
to spawn accept_peer_video as a background task instead of awaiting camera
initialization inline, allowing the signaling loop to continue processing
events. Move the success/failure logging and activate_peer_video_request call
into the spawned task, preserving the existing behavior after acceptance
completes.
- Around line 1276-1285: Update activate_peer_video_request to use
video_ui.get_mut(call_id) and mutate the existing entry in place when it matches
VideoUi::PendingPeerRequest(request), replacing it with VideoUi::Active without
calling insert or allocating a new String key.

---

Outside diff comments:
In `@src/handlers/call.rs`:
- Around line 223-387: The transition serialization in the video-state handling
block intentionally holds _transition_guard across the awaited accept and
enabled client.send_node calls. Preserve this locking scope and ordering so peer
signals and local actions remain serialized through the handshake announcements;
make no code change unless future requirements explicitly address the resulting
latency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: babdfedb-12c8-45cb-8728-d5cb23989fbf

📥 Commits

Reviewing files that changed from the base of the PR and between 4d9e8ed and 8c48402.

📒 Files selected for processing (10)
  • Cargo.toml
  • examples/voip-cli/src/main.rs
  • src/client/voip.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs

Comment thread examples/voip-cli/src/main.rs
Comment thread examples/voip-cli/src/main.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad0d0def83

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1430 to +1431
tokio::spawn(async move {
if let Err(e) = accept_peer_video(&handle, request).await {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel stale auto-accept source setup

In --video auto-accept mode, a peer cancel/re-request while the first accept_peer_video is still waiting for ffmpeg's first IDR now starts another detached task; the stale task is not cancelled and the token is not checked until after spawn_video_source opens /dev/video0, so the new request can fail with the camera still busy. Keep the task tied to the current token/cancel event or claim the request before opening the source.

Useful? React with 👍 / 👎.

Comment thread src/voip/facade.rs
Comment on lines +1649 to +1651
self.release_local_video();
self.client_registry
.stop_local_video(&self.call_id, self.generation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep inbound video enabled when stopping local video

When a bidirectional video call is reduced only on our side, stop_local_video leaves the peer direction active, but release_local_video() detaches endpoints and sends VideoControl::Disable; the driver maps that to CallEngine::disable_video(), which ignores inbound video. As a result, stop_video() also stops rendering the peer's still-active stream until another upgrade re-enables the whole plane.

Useful? React with 👍 / 👎.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Jul 17, 2026
@greptile-apps
greptile-apps Bot dismissed their stale review July 17, 2026 02:24

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 360b71a143

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +26 to +29
pub struct VideoUpgradeToken {
generation: u64,
epoch: u64,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind upgrade tokens to their originating registry

When an application operates multiple Client instances, each registry starts its generation counter at zero and each call starts its request epoch at zero, so the first peer request in two registries produces the identical { generation: 0, epoch: 1 } token. accept_video validates only these two values against the target handle's registry, allowing a token received from one client to accept and attach endpoints to a different client's pending request; include registry/request identity that cannot collide across client instances.

Useful? React with 👍 / 👎.

@jlucaso1
jlucaso1 merged commit 7df3a3c into main Jul 24, 2026
18 checks passed
@jlucaso1
jlucaso1 deleted the agent/fix-video-upgrade-state-machine branch July 24, 2026 20:36
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.

1 participant