{"record":{"id":"439c5827e916162d","repo":"hcengineering/platform","slug":"cannot-stop-from-state-state","errorCode":null,"errorMessage":"Cannot stop from state: ${state}","messagePattern":"Cannot stop from state: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/recorder-resources/src/stores/recorder.ts","lineNumber":147,"sourceCode":"\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\n      if (state !== 'recording' && state !== 'paused') {\n        throw new Error(`Cannot stop from state: ${state}`)\n      }\n\n      if (screenRecorder === null) {\n        throw new Error('No active recording')\n      }\n\n      updateStore({ state: 'stopping' })\n      stopElapsedTimer()\n\n      const result = await screenRecorder.stop()\n      updateStore({ state: 'stopped', result })\n\n      manager.cleanup()\n      composer.cleanup()\n\n      screenRecorder = null\n    },\n","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/recorder-resources/src/stores/recorder.ts#L129-L165","documentation":"`stop()` may only run while the recorder is 'recording' or 'paused'. This error is thrown when `recorder.stop()` is called from any other state ('idle', 'ready', 'stopping', 'stopped'), meaning there is no recording in progress to finalize. It exists so the recorder does not attempt to finalize a MediaRecorder that does not exist or is already being finalized.","triggerScenarios":"Calling `recorder.stop()` twice (the second call sees 'stopped'); calling `stop()` before any recording started ('idle'/'ready'); calling `stop()` while a previous `stop()` is still awaiting `screenRecorder.stop()` ('stopping'); the stream-ended callback firing after `stop()` already transitioned the state.","commonSituations":"Double-click on a stop button; a UI calling stop both from a stream-ended event handler (`onStreamEnded`) and from a manual stop action; unmount cleanup code calling `stop()` after the user already stopped; tests tearing down while state is 'ready'.","solutions":["Guard the call: only invoke `stop()` when the store's `state` is 'recording' or 'paused'.","Keep a reference to the in-flight stop promise and reuse/await it instead of calling `stop()` again on double clicks.","Use `recorder.cancel()` or `cleanup()` when you want to discard/reset from a non-active state instead of `stop()`.","Disable the stop control unless `state === 'recording' || state === 'paused'`."],"exampleFix":"// before\nasync function stopRecording () {\n  await recorder.stop()\n}\n\n// after\nlet stopping: Promise<void> | null = null\nasync function stopRecording () {\n  if (stopping !== null) return stopping\n  const { state } = getStore(recorder)\n  if (state !== 'recording' && state !== 'paused') return\n  stopping = recorder.stop().finally(() => { stopping = null })\n  return stopping\n}","handlingStrategy":"validation","validationCode":"const { state } = get(recorder)\nif (state !== 'recording' && state !== 'paused') return // nothing to stop\nawait recorder.stop()","typeGuard":"function canStop(state: RecorderState): state is 'recording' | 'paused' {\n  return state === 'recording' || state === 'paused'\n}","tryCatchPattern":"try {\n  await recorder.stop()\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Cannot stop from state:')) {\n    // already stopped or never started — safe to ignore and reset UI\n  } else throw err\n}","preventionTips":["Store the in-flight stop promise and return it for repeated stop requests (idempotent wrapper).","Disable the stop button unless `state === 'recording' || state === 'paused'`.","Coordinate the stream-ended auto-stop with manual stop so only one path calls `stop()`.","Use `cancel()`/`cleanup()` for reset-from-any-state semantics instead of `stop()`."],"tags":["state-machine","recorder","invalid-state-transition","double-stop"],"backgroundTag":"invalid-state-transition","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}