Skip to content
Latchkey

Vitest "Vitest caught X unhandled error" in CI

A promise rejection or error escaped any test and was caught by Vitest at the process level. Even if every assertion passed, the dangling error fails the run.

What this error means

CI shows green test results followed by "Vitest caught N unhandled error(s) during the test run" and a non-zero exit. The stack often points at a timer, listener, or fetch started in a test that already finished.

node
Vitest caught 1 unhandled error during the test run.
This was likely caused by a rejection that was not awaited.

Unhandled Rejection: TypeError: fetch failed

Common causes

A promise was started but never awaited

A test kicked off async work (fetch, setTimeout callback) and returned before it settled, so the rejection surfaced after teardown.

A listener or interval kept running

An event listener or setInterval registered in a test fired after the test ended and threw, with no test frame to catch it.

How to fix it

Await all async work inside the test

Return or await every promise so rejections are attributed to the test that owns them.

test
it('loads data', async () => {
  await expect(loadData()).resolves.toBeDefined();
});

Tear down timers and listeners

Clear intervals and remove listeners in afterEach so nothing fires after the test completes.

  1. Track every timer/listener created in a test.
  2. Clear them in afterEach (clearInterval, removeListener, abort controllers).
  3. Use vi.useFakeTimers() where appropriate and restore in afterEach.

How to prevent it

  • Always await or return promises in tests.
  • Clean up timers, intervals, and listeners in afterEach.
  • Use an AbortController to cancel in-flight requests on teardown.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →