hcengineering/platform · error

No active recording

Error message

No active recording

What it means

This is an internal-consistency guard in `stop()`: even though the state check ('recording'/'paused') passed, the module-level `screenRecorder` handle is `null`, so there is no underlying ScreenRecorder to finalize. Normally unreachable from the public API, it surfaces if the handle was cleared (via `cancel`/`cleanup`) without the store state being updated, or due to concurrent async operations mutating state between the two checks.

Source

Thrown at plugins/recorder-resources/src/stores/recorder.ts:151

      // Create and start screen recorder
      screenRecorder = await createScreenRecorder(stream)

      await screenRecorder.start()

      startElapsedTimer()
      updateStore({ state: 'recording' })
    },

    async stop (): Promise<void> {
      const { state } = get(store)

      if (state !== 'recording' && state !== 'paused') {
        throw new Error(`Cannot stop from state: ${state}`)
      }

      if (screenRecorder === null) {
        throw new Error('No active recording')
      }

      updateStore({ state: 'stopping' })
      stopElapsedTimer()

      const result = await screenRecorder.stop()
      updateStore({ state: 'stopped', result })

      manager.cleanup()
      composer.cleanup()

      screenRecorder = null
    },

    async pause (): Promise<void> {
      const { state } = get(store)
      if (state !== 'recording') {
        throw new Error(`Cannot pause from state: ${state}`)

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Serialize lifecycle calls: never invoke `stop()` and `cancel()`/`cleanup()` concurrently; await one before the other.
  2. Re-read the store state immediately before calling `stop()` and skip if it is not 'recording'/'paused'.
  3. Prefer `cancel()` when you intend to discard the recording — it already handles the null `screenRecorder` case safely.
  4. If this occurs without concurrent calls, report it: it indicates the recorder's state and handle are out of sync (library bug).

Example fix

// before
await Promise.all([recorder.stop(), recorder.cleanup()])

// after
await recorder.stop()
await recorder.cleanup()
Defensive patterns

Strategy: try-catch

Validate before calling

const { state } = get(recorder)
if (state !== 'recording' && state !== 'paused') return
// also ensure no cancel/cleanup is concurrently in flight
if (lifecycleBusy) return
await recorder.stop()

Type guard

function isStoppable(state: RecorderState): boolean {
  return (state === 'recording' || state === 'paused')
}

Try / catch

try {
  await recorder.stop()
} catch (err) {
  if (err instanceof Error && err.message === 'No active recording') {
    await recorder.cleanup() // restore consistent idle state
  } else throw err
}

Prevention

When it happens

Trigger: Calling `stop()` concurrently with `cancel()` or `cleanup()` (both null out `screenRecorder`); a race where cleanup finishes between the state read and the null check; calling `stop()` after an awaited `cleanup()` resolved while a stale captured state read said 'recording'.

Common situations: Component unmount cleanup racing a user-initiated stop; a page-hide/visibility handler calling `cancel()` while the app also calls `stop()` on the same event; tests that call cleanup and stop in parallel.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/c63bcfe4e50bf52e. Report an issue: GitHub.