Skip to content

fix: clash agents silent — use Chromium new headless so browser audio renders#76

Merged
guohai merged 1 commit into
mainfrom
fix/clash-browser-audio-new-headless
Jul 17, 2026
Merged

fix: clash agents silent — use Chromium new headless so browser audio renders#76
guohai merged 1 commit into
mainfrom
fix/clash-browser-audio-new-headless

Conversation

@guohai

@guohai guohai commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

A live clash match produced audible moderator but completely silent agents. Inspecting the broadcasters' captured audio in the running runner container showed both agents' output was perfect digital silence:

Capture RMS Meaning
agent_a_in / agent_b_in (mic feeds) 0.0168 agents hear fine ✅
agent_a_out / agent_b_out (→ uid 100/200) 0.000000 agents emit nothing

Root cause

browser-agent.ts launched Chromium with headless: true — Playwright's old headless mode (the "headless shell"), which has no audio stack. The agents' TTS/WebRTC voice plays inside the page but is never rendered to the PulseAudio sink, so agora-broadcaster captures and publishes silence. The agents' microphones still work because those are fed by PipeWire pacat/loopback (not the browser) — which is exactly why the moderator was audible while the agents were mute.

Fix

Launch with new headless mode (--headless=new), which runs the full Chromium including the audio pipeline, with no X server required. Mirrors voice-agent-tester, which already uses new headless for the same reason.

Validation

Empirically verified in the live runner container with a WebAudio tone probe capturing Sink_A_Out:

  • old headless (headless: true): RMS 0.000000
  • new headless (--headless=new): RMS 0.199595

Typecheck clean; all 64 clash unit tests pass (clash-audio, clash-runner).

Deploy note

Runner-side change — the vox-clash-runner image must be rebuilt (CI on merge, since vox_clash_runner/** changed) and redeployed via vox-upgrade.sh for this to take effect. No server/schema changes.

Generated with SMT smt@agora.io

… renders

Clash agents broadcast pure 0.000000 silence: the runner launched Chromium
with `headless: true`, which is Playwright's OLD headless ("headless shell")
that has no audio stack. The agents' TTS/WebRTC voice plays inside the page
but is never rendered to the PulseAudio sink, so agora-broadcaster captures
and publishes silence. (The agents' MICs still work — those are fed by
PipeWire pacat/loopback, not the browser — which is why the moderator was
audible but neither agent was.)

Fix: launch with new headless mode (`--headless=new`), which runs the full
Chromium including the audio pipeline, no X server required. Empirically
verified in the live runner container: old headless → Sink_A_Out RMS
0.000000; new headless → RMS 0.199. Mirrors voice-agent-tester, which already
uses new headless for the same reason.

Runner image must be rebuilt (CI on merge) and redeployed (vox-upgrade.sh)
for this to take effect.

🤖 Built with SMT <smt@agora.build>
@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

This is a focused, low-risk change. The only functional modification is in browser-agent.ts; the Dockerfile change is comment-only (verified).

Assessment: Looks good. The headless: false + --headless=new combination is the correct and established Playwright workaround for getting Chromium's full audio stack (old "headless shell" ships without one). It matches the voice-agent-tester approach noted in the comment, needs no X server, and the doc/inline comments clearly explain the non-obvious why — which is exactly right for a subtle flag like this.

A few notes, none blocking:

  • No security regression. --no-sandbox and --use-fake-ui-for-media-stream were already present; the diff adds only --headless=new. No new credential surface, and the PULSE env passthrough is unchanged.
  • Minor robustness thought (not a blocker): because headless: false now drives launch, this container hard-depends on Chromium's new-headless path staying available. If a future Chromium ever drops/renames --headless=new, the browser would try to open a real window and fail in the container (no display). Worth being aware of at Playwright/Chromium upgrade time, but no action needed now.
  • Consistency check worth a glance: confirm the audio-pipeline test (test-audio-pipeline.sh) and any other Playwright launch sites in the clash runner use the same flag, so behavior doesn't diverge between the tested path and the runtime path. The PR only touches this one launch site, which is likely the only one, but it's cheap to verify.

The change is well-scoped, correctly reasoned, and the comments preserve the rationale for future maintainers. Ship it.

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

I reviewed the two changed files in PR #76 and don’t see any real security, logic, performance, or maintainability issues from the change. The headless: false + --headless=new combination is intentional here to force full Chromium/new headless behavior while avoiding the old headless shell audio limitation.

Checked:

  • vox_clash_runner/browser-agent.ts
  • vox_clash_runner/Dockerfile
  • git diff --check 3536cb6..HEAD passed

Residual risk: I didn’t run the clash tests or container audio validation locally in this read-only environment, so this review is based on code inspection only.

@guohai
guohai merged commit 02b89d1 into main Jul 17, 2026
3 checks passed
@guohai
guohai deleted the fix/clash-browser-audio-new-headless branch July 17, 2026 11:35
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.

1 participant