{"record":{"id":"c63bcfe4e50bf52e","repo":"hcengineering/platform","slug":"no-active-recording","errorCode":null,"errorMessage":"No active recording","messagePattern":"No active recording","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/recorder-resources/src/stores/recorder.ts","lineNumber":151,"sourceCode":"\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\n    async pause (): Promise<void> {\n      const { state } = get(store)\n      if (state !== 'recording') {\n        throw new Error(`Cannot pause from state: ${state}`)","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/recorder-resources/src/stores/recorder.ts#L133-L169","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Serialize lifecycle calls: never invoke `stop()` and `cancel()`/`cleanup()` concurrently; await one before the other.","Re-read the store state immediately before calling `stop()` and skip if it is not 'recording'/'paused'.","Prefer `cancel()` when you intend to discard the recording — it already handles the null `screenRecorder` case safely.","If this occurs without concurrent calls, report it: it indicates the recorder's state and handle are out of sync (library bug)."],"exampleFix":"// before\nawait Promise.all([recorder.stop(), recorder.cleanup()])\n\n// after\nawait recorder.stop()\nawait recorder.cleanup()","handlingStrategy":"try-catch","validationCode":"const { state } = get(recorder)\nif (state !== 'recording' && state !== 'paused') return\n// also ensure no cancel/cleanup is concurrently in flight\nif (lifecycleBusy) return\nawait recorder.stop()","typeGuard":"function isStoppable(state: RecorderState): boolean {\n  return (state === 'recording' || state === 'paused')\n}","tryCatchPattern":"try {\n  await recorder.stop()\n} catch (err) {\n  if (err instanceof Error && err.message === 'No active recording') {\n    await recorder.cleanup() // restore consistent idle state\n  } else throw err\n}","preventionTips":["Never run `stop()` and `cancel()`/`cleanup()` concurrently; serialize lifecycle calls.","Track a `lifecycleBusy` flag around all recorder mutations.","After this error, call `cleanup()` to realign store state with the internal handle.","Report reproducible single-threaded occurrences as a library state-desync bug."],"tags":["recorder","internal-state","race-condition","null-reference"],"backgroundTag":"recorder-state-desync","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}