# RB-01 CRITICAL: racy single-session guard in live.rs

**Severity:** CRITICAL
**Path:** `src-tauri/src/commands/live.rs:193-338`
**Source:** [2026-04-22 code review](../code-review-2026-04-22.md#c1--racy-single-session-guard-in-livers)
**Labels:** release-blocker, critical, concurrency
**Status:** RESOLVED (2026-04-22)

## Resolution

`LiveTranscriptionState` now includes a dedicated
`tokio::sync::Mutex<()>` lifecycle gate. Both
`start_live_transcription_session` and
`stop_live_transcription_session` acquire that async mutex before
touching `running`, and they keep it held across the awaited setup /
join work that previously exposed the race windows.

That changes the two failing interleavings from the review:

- Two overlapping starts no longer race through the empty-slot check.
  The second call waits for the first to finish setup, then observes
  `running.is_some()` and returns the existing
  `"A live transcription session is already running"` error.
- A start launched during stop can no longer sneak in after
  `running.take()` but before the previous worker has fully joined.
  It blocks on the lifecycle mutex until the join completes.

Regression tests in `commands::live::tests`:

- `concurrent_starts_allow_only_one_session_to_claim_the_slot`
- `start_waits_for_stop_to_finish_joining_before_reusing_slot`

## Problem

`start_live_transcription_session` checks `running` is `None` before multiple `await`s and only stores the handle at the end. `stop_live_transcription_session` removes `running` before awaiting the worker join. Two overlapping IPC calls can:

- Admit a second live session (start sees `running == None`, awaits, another start fires in the gap, both proceed)
- Expose an empty slot while the first session is still shutting down (stop removes the handle, awaits, a fresh start runs against the incoherent state)

This breaks the file's core invariant that only one microphone/live session exists at a time.

## Acceptance

- Hold the session-slot lock (or a semaphore) across the async boundary so no two `start`/`stop` IPC calls can interleave.
- Regression test: fire two `start_live_transcription_session` IPC calls concurrently; exactly one must succeed and the other must error cleanly.
- Regression test: during an in-flight `stop`, a concurrent `start` must block until the previous session's worker has fully joined.

## Fix scope

Large. Will likely require the `run_live_session` monolith refactor (RB-04) to land first so the state machine is small enough to reason about under the lock discipline.

## Dependencies

- Landed after RB-04 (`run_live_session` refactor) made the worker lifecycle explicit enough to guard safely.
