{"record":{"id":"fcb7be5a38d88f6a","repo":"hcengineering/platform","slug":"cannot-start-recording-from-state-state","errorCode":null,"errorMessage":"Cannot start recording from state: ${state}","messagePattern":"Cannot start recording from state: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/recorder-resources/src/stores/recorder.ts","lineNumber":127,"sourceCode":"    async initialize (config: RecorderConfig): Promise<void> {\n      const { state } = get(store)\n\n      if (state !== 'idle') {\n        console.warn('Recorder is already initialized')\n        return\n      }\n\n      manager.initialize()\n      composer.initialize()\n\n      updateStore({ state: 'ready', config })\n    },\n\n    async start (): Promise<void> {\n      const { state } = get(store)\n\n      if (state !== 'ready' && state !== 'stopped') {\n        throw new Error(`Cannot start recording from state: ${state}`)\n      }\n\n      const { screenStream, micStream } = get(manager)\n      const canvasStream = composer.getStream()\n      const stream = combineStreams(canvasStream, micStream, screenStream)\n\n      // Create and start screen recorder\n      screenRecorder = await createScreenRecorder(stream)\n\n      await screenRecorder.start()\n\n      startElapsedTimer()\n      updateStore({ state: 'recording' })\n    },\n\n    async stop (): Promise<void> {\n      const { state } = get(store)\n","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/recorder-resources/src/stores/recorder.ts#L109-L145","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Check the recorder's current state via the store before calling start(): only call it when `state === 'ready' || state === 'stopped'`.","If state is 'idle', call `await recorder.initialize(config)` first, then `start()`.","If state is 'recording' or 'paused', the recording is already running — resume/continue instead of starting again.","If state is 'stopping', await the in-flight `stop()` promise (store it from the caller) before starting a new session.","Disable/start-guard the UI record control by subscribing to the recorder store state so the button is only enabled in 'ready'/'stopped'."],"exampleFix":"// before\nbutton.onclick = () => { void recorder.start() }\n\n// after\nbutton.onclick = () => {\n  const { state } = getStore(recorder)\n  if (state !== 'ready' && state !== 'stopped') return\n  void recorder.start()\n}","handlingStrategy":"validation","validationCode":"import { get } from 'svelte/store'\nconst { state } = get(recorder)\nif (state !== 'ready' && state !== 'stopped') {\n  throw new Error(`start skipped: recorder in state ${state}`)\n}","typeGuard":"function canStart(state: RecorderState): state is 'ready' | 'stopped' {\n  return state === 'ready' || state === 'stopped'\n}","tryCatchPattern":"try {\n  await recorder.start()\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Cannot start recording from state:')) {\n    // re-sync: read recorder state, route to resume()/initialize() instead\n  } else throw err\n}","preventionTips":["Subscribe to the recorder store and derive button enabled/disabled from `state`.","Always `await recorder.initialize(config)` exactly once before any `start()`.","Debounce record-button clicks and disable while a lifecycle promise is in flight.","Keep a single owner module for start/stop/pause/resume calls to avoid racy call sites."],"tags":["state-machine","recorder","invalid-state-transition","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}