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