Zackriya-Solutions/meetily · warning · anyhow::Error

Recording is already paused

Error message

Recording is already paused

What it means

pause_recording was called while is_paused is already true - a double-pause. The guard makes repeated pause fail rather than silently restarting the pause timer, which would otherwise corrupt total_pause_duration accounting.

Source

Thrown at frontend/src-tauri/src/audio/recording_state.rs:182

        // Clear pause tracking when stopping
        *self.pause_start.lock().unwrap() = None;
        // CRITICAL: Clear audio sender to close the pipeline channel
        // This ensures the pipeline loop exits properly after processing all chunks
        *self.audio_sender.lock().unwrap() = None;
        // CRITICAL: Clear device references to release microphone/speaker
        // Without this, Arc<AudioDevice> references persist and keep the mic active
        *self.microphone_device.lock().unwrap() = None;
        *self.system_device.lock().unwrap() = None;
        *self.disconnected_device.lock().unwrap() = None;
        log::info!("Recording stopped, device references cleared");
    }

    pub fn pause_recording(&self) -> Result<()> {
        if !self.is_recording() {
            return Err(anyhow::anyhow!("Cannot pause when not recording"));
        }
        if self.is_paused() {
            return Err(anyhow::anyhow!("Recording is already paused"));
        }

        self.is_paused.store(true, Ordering::SeqCst);
        *self.pause_start.lock().unwrap() = Some(Instant::now());
        log::info!("Recording paused");
        Ok(())
    }

    pub fn resume_recording(&self) -> Result<()> {
        if !self.is_recording() {
            return Err(anyhow::anyhow!("Cannot resume when not recording"));
        }
        if !self.is_paused() {
            return Err(anyhow::anyhow!("Recording is not paused"));
        }

        // Calculate pause duration and add to total
        if let Some(pause_start) = self.pause_start.lock().unwrap().take() {

View on GitHub (pinned to 0281737d87)

Solutions

  1. Make the caller idempotent: treat 'already paused' as success
  2. Debounce or disable the Pause button once the paused state is confirmed
  3. Check is_recording_paused before sending the command

Example fix

// before
state.pause_recording()?;

// after - idempotent pause at the call site
match state.pause_recording() {
    Ok(()) => {}
    Err(e) if e.to_string().contains("already paused") => { /* desired state reached */ }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: validation

Validate before calling

// Frontend: only pause when actually unpaused
if ((await invoke<boolean>('is_recording')) && !(await invoke<boolean>('is_recording_paused'))) {
  await invoke('pause_recording');
}

Try / catch

Treat 'Recording is already paused' as success (the desired state is already reached); only surface other errors.

Prevention

When it happens

Trigger: Double-click on the Pause button; a keyboard shortcut and a UI button both firing; retry of a pause command that already succeeded but whose result was not observed.

Common situations: Missing debounce on UI buttons; event replay in tests; race between an automatic pause (device reconnect) and a user pause.

Related errors


AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16). Data as JSON: /api/errors/d231cd09be5e7c70. Report an issue: GitHub.