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
- 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).
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
- 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.
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
- Cannot start recording from state: ${state}
- Cannot stop from state: ${state}
- Cannot pause from state: ${state}
- Cannot resume from state: ${state}
- Reaction not found.
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/c63bcfe4e50bf52e.
Report an issue: GitHub.