remotion-dev/remotion · error · Error

onError was used but did not return an "action" field. See d

Error message

onError was used but did not return an "action" field. See docs for this API on how to use onError.

What it means

When a parse iteration throws and an onError callback was supplied, parseLoop awaits onError(e) and inspects the returned object. If that object has no `action` field the parser throws, because it cannot decide whether to fail or continue. The contract requires onError to return `{action: 'fail' | 'download'}`.

Source

Thrown at packages/media-parser/src/parse-loop.ts:149

						iterator: state.iterator,
						logLevel: state.logLevel,
						mode: state.mode,
						contentLength: state.contentLength,
						seekInfiniteLoop: state.seekInfiniteLoop,
						currentReader: state.currentReader,
						readerInterface: state.readerInterface,
						fields: state.fields,
						src: state.src,
						discardReadBytes: state.discardReadBytes,
						prefetchCache: state.prefetchCache,
						isoState: state.iso,
					});
					state.timings.timeSeeking += Date.now() - seekStart;
				}
			} catch (e) {
				const err = await onError(e as Error);
				if (!err.action) {
					throw new Error(
						'onError was used but did not return an "action" field. See docs for this API on how to use onError.',
					);
				}

				if (err.action === 'fail') {
					throw e;
				}

				if (err.action === 'download') {
					state.errored = e as Error;
					Log.verbose(
						state.logLevel,
						'Error was handled by onError and deciding to continue.',
					);
				}
			}
		}

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Make onError return an object with action: `{action: 'fail'}` to re-throw, or `{action: 'download'}` to continue parsing.
  2. If you only want to log, return `{action: 'fail'}` after logging so the original error still propagates.
  3. Add a TypeScript annotation `onError: ParseMediaOnError` so a missing action is caught at compile time.
  4. Read the media-parser onError docs for the full return contract.

Example fix

// before
await parseMedia({src, onError: (e) => console.error(e), fields: {}});

// after
await parseMedia({
  src,
  onError: (e) => {
    console.error(e);
    return {action: 'fail'};
  },
  fields: {},
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate your onError returns the right shape before passing it in
function makeOnError(log: (e: Error) => void) {
  return (e: Error): {action: 'fail' | 'download'} => {
    log(e);
    return {action: 'fail'};
  };
}

Type guard

import type {ParseMediaOnError} from '@remotion/media-parser';
const isOnErrorResult = (v: unknown): v is {action: 'fail' | 'download'} =>
  typeof v === 'object' && v !== null &&
  (v as any).action === 'fail' || (v as any).action === 'download';

Try / catch

// Not applicable — this error IS the result of a bad onError contract.
// Fix: ensure onError always returns {action: 'fail' | 'download'}.
await parseMedia({
  src,
  onError: (e) => { console.error(e); return {action: 'fail'}; },
  fields: {},
});

Prevention

When it happens

Trigger: Supplying an onError that returns undefined, null, `{}`, or an object missing the action key — e.g. `onError: (e) => console.error(e)` (returns undefined) or `onError: () => ({retry: true})`.

Common situations: Treating onError like a logging callback instead of a recovery decision function; copy-pasting from examples that omit the return; migration from an older API shape.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/8a339d5cb2b30223. Report an issue: GitHub.