hcengineering/platform · error

Cannot start recording from state: ${state}

Error message

Cannot start recording from state: ${state}

What it means

The recorder's `start()` enforces a state machine: recording may only begin from 'ready' (just initialized) or 'stopped' (a previous recording finished). This error means `recorder.start()` was invoked while the recorder was in some other lifecycle state ('idle', 'recording', 'paused', or 'stopping'), so it refuses to begin a new session. It protects the single `screenRecorder` instance from being created twice or started before media streams/composer are set up.

Source

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

    async initialize (config: RecorderConfig): Promise<void> {
      const { state } = get(store)

      if (state !== 'idle') {
        console.warn('Recorder is already initialized')
        return
      }

      manager.initialize()
      composer.initialize()

      updateStore({ state: 'ready', config })
    },

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

      if (state !== 'ready' && state !== 'stopped') {
        throw new Error(`Cannot start recording from state: ${state}`)
      }

      const { screenStream, micStream } = get(manager)
      const canvasStream = composer.getStream()
      const stream = combineStreams(canvasStream, micStream, screenStream)

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

      await screenRecorder.start()

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

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

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check the recorder's current state via the store before calling start(): only call it when `state === 'ready' || state === 'stopped'`.
  2. If state is 'idle', call `await recorder.initialize(config)` first, then `start()`.
  3. If state is 'recording' or 'paused', the recording is already running — resume/continue instead of starting again.
  4. If state is 'stopping', await the in-flight `stop()` promise (store it from the caller) before starting a new session.
  5. Disable/start-guard the UI record control by subscribing to the recorder store state so the button is only enabled in 'ready'/'stopped'.

Example fix

// before
button.onclick = () => { void recorder.start() }

// after
button.onclick = () => {
  const { state } = getStore(recorder)
  if (state !== 'ready' && state !== 'stopped') return
  void recorder.start()
}
Defensive patterns

Strategy: validation

Validate before calling

import { get } from 'svelte/store'
const { state } = get(recorder)
if (state !== 'ready' && state !== 'stopped') {
  throw new Error(`start skipped: recorder in state ${state}`)
}

Type guard

function canStart(state: RecorderState): state is 'ready' | 'stopped' {
  return state === 'ready' || state === 'stopped'
}

Try / catch

try {
  await recorder.start()
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Cannot start recording from state:')) {
    // re-sync: read recorder state, route to resume()/initialize() instead
  } else throw err
}

Prevention

When it happens

Trigger: Calling `recorder.start()` before `recorder.initialize(config)` was awaited (state 'idle'); calling `start()` while already 'recording'; calling `start()` while 'paused'; calling `start()` re-entrantly during a 'stopping' transition (e.g. a UI button double-click that races `stop()`); or after `cleanup()` returned the recorder to 'idle'.

Common situations: A UI with a record button that is not disabled based on the store's `state`; a component remounting and calling `start()` without re-initializing; an automated test invoking `start()` without the initialize step; a race between the stream-ended callback (which auto-stops) and a user clicking stop-then-start quickly.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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