Skip to content

DX-818: Add realtime transcription support with failover handling#450

Open
yadavsahil197 wants to merge 4 commits into
mainfrom
syadav/realtime-transcription
Open

DX-818: Add realtime transcription support with failover handling#450
yadavsahil197 wants to merge 4 commits into
mainfrom
syadav/realtime-transcription

Conversation

@yadavsahil197

Copy link
Copy Markdown

No description provided.

@broly-code-security-scanner

broly-code-security-scanner Bot commented Jul 16, 2026

Copy link
Copy Markdown

Broly Security Scan

Note

Summary

6 actionable finding(s) in this PR

  • 🟡 6 medium

5 highest-priority actionable rows in the table below (critical/high first, then top medium).

Severity Scanner Issue Location Dismiss Verdict
🟡 MEDIUM SCA aiohttp@3.13.3 — 42 vulnerabilities (worst:
GHSA-2fqr-mr3j-6wp8)
→ >= 3.14.1
uv.lock:1 d1 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SCA pygments@2.19.2 — 2 vulnerabilities (worst:
GHSA-5239-wwwm-4pmq)
→ >= 2.20.0
uv.lock:1 d4 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SCA pillow@12.1.1 — 18 vulnerabilities (worst:
GHSA-5xmw-vc9v-4wf2)
→ >= 12.3.0
uv.lock:1 d3 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SCA idna@3.11 — 2 vulnerabilities (worst:
GHSA-65pc-fj4g-8rjx)
→ >= 3.15
uv.lock:1 d2 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SCA pytest@9.0.2 — 2 vulnerabilities (worst:
GHSA-6w46-j5rx-g56g)
→ >= 9.0.3
uv.lock:1 d5 🔺 TRUE_POSITIVE · Confidence: HIGH

Dismiss false positives

Each dismissable row has a Dismiss key (d1, d2, …) in the table above. Reply to this comment (or post on the PR) with /broly dismiss d2: your reason to mark a false positive, or /broly undismiss d2 to reverse it. Broly records every dismissal in the section below this comment (updated in place) and suppresses the finding on the next scan.

Key Finding
d1 🟡 MEDIUM · aiohttp@3.13.3 — 42 vulnerabilities (worst: GHSA-2fqr-mr3j-6wp8) · uv.lock:1
d2 🟡 MEDIUM · idna@3.11 — 2 vulnerabilities (worst: GHSA-65pc-fj4g-8rjx) · uv.lock:1
d3 🟡 MEDIUM · pillow@12.1.1 — 18 vulnerabilities (worst: GHSA-5xmw-vc9v-4wf2) · uv.lock:1
d4 🟡 MEDIUM · pygments@2.19.2 — 2 vulnerabilities (worst: GHSA-5239-wwwm-4pmq) · uv.lock:1
d5 🟡 MEDIUM · pytest@9.0.2 — 2 vulnerabilities (worst: GHSA-6w46-j5rx-g56g) · uv.lock:1
d6 🟡 MEDIUM · urllib3@2.6.3 — 4 vulnerabilities (worst: GHSA-mf9v-mfxr-j63j) · uv.lock:1

Note

Re-scan this PR anytime with /broly scan — useful after /broly undismiss, or to refresh findings without a new push.

Broly — SAST (zai-org/GLM-5.2) · Secrets · SCA · GH Actions (zizmor) · Containers · SBOM · Powered by Together AI

Comment thread examples/realtime_transcription.py Outdated
Comment thread examples/realtime_failover.py Outdated

@blainekasten blainekasten 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.

This looks great!

As we discussed in slack, let's move this functionality under the beta namespace for now to give us some wiggle room if we decide to change the api interface in the near term.

@yadavsahil197
yadavsahil197 force-pushed the syadav/realtime-transcription branch from 85d5879 to 6d913ab Compare July 16, 2026 21:20
@yadavsahil197

Copy link
Copy Markdown
Author

Moved to the beta namespace as discussed!

@yadavsahil197 yadavsahil197 changed the title Add realtime transcription support with failover handling DX-818: Add realtime transcription support with failover handling Jul 16, 2026
Comment thread examples/realtime_failover.py Outdated
@zainhas
zainhas self-requested a review July 18, 2026 01:15
for _held, reclaim in holders:
if deficit <= 0:
return
deficit -= reclaim(deficit)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think there's a cross-thread mutation hazard here. charge() runs on whatever thread is appending, and when the pool is under pressure this ends up calling reclaimAudioBuffer.trim_to on other sessions buffers. But those sessions are running on their own event loops so we can popleft from a deque while its owner's writer loop is mid-iteration in read_from which will be a error.

It would only fire under pool pressure with multiple sessions - could we marshal the reclaim onto the holder's loop via call_soon_threadsafe or put a lock around AudioBuffer?

pool=pool,
)
self._pool = pool
pool.register(self, lambda: self.state.buffer.size, self.state.reclaim)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Registering in init but only unregistering in close()/_fail() means a session that's constructed but never started (or just abandoned) stays strongly referenced by the client-scoped pool forever. Could we move registration into start(), or make _holders a WeakKeyDictionary so the pool never keeps a session alive on its own?

# 300s no-append idle timeout
if self._keepalive_silence and self._last_append_at:
if now - self._last_append_at > 200.0:
await self.append(b"\x00" * int(self.state.bps * 0.1)) # 100ms of silence

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This append() can raise a RealtimeBufferOverflowError when overflow="error" or a stored terminal failure racing in via _raise_if_failed() and _watchdog_loop only handles CancelledError. So the watchdog task dies with an unobserved exception and we lose echo probing + keepalive while the session looks healthy. Worth wrapping the loop body in an except Exception that at least logs.

self._writer_wakeup.set()
for task in (self._watchdog_task, self._writer_task, self._reconnect_task, self._reader_task):
if task is not None:
task.cancel()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We cancel these but never await them, and in the sync the loop gets stopped right after so tasks can be destroyed while still pending (asyncio warnings, and the reader may not finish tearing down the socket). A gather(*tasks, return_exceptions=True) after the cancels would make shutdown deterministic.

self._session = self._call(_create())

def __enter__(self) -> "RealtimeTranscriptionSession":
self.start()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably want a try/except around this that calls close() before re-raising. If start() raises here (e.g. bad base_url → RealtimeConnectionError), exit never runs so the background loop thread we spawned in init lives until atexit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants