vitest-dev/vitest · error · Error

Cannot annotate tests outside of the test run. The test

Error message

Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.

What it means

Thrown by context.annotate when the owning test already has a result with a non-'run' state, meaning the test has finished (passed/failed/skipped). Annotations are metadata attached during the live test run; annotating after completion has nowhere to attach. The check reads `test.result.state` and rejects anything other than 'run' or no result.

Solutions

  1. Move the annotate call inside the test body, before it returns.
  2. If annotating on failure, attach the diagnostic to the error itself (via expect.assertions or a custom assertion) rather than calling annotate from onTestFailed.
  3. Guard the call: check that the test is still running before annotating.

Example fix

// before
test('x', () => {
  startBackgroundWork()
})
onTestFailed(() => {
  ctx.annotate('late diagnostic') // throws
})

// after
test('x', async () => {
  const result = await startBackgroundWork()
  ctx.annotate('diagnostic', { body: result })
})
Defensive patterns

Strategy: validation

Validate before calling

function safeAnnotate(ctx, msg, att) {
  const t = ctx.task
  if (t.result && t.result.state !== 'run') return
  return ctx.annotate(msg, att)
}

Type guard

const testIsRunning = (t: Test) =>
  !t.result || t.result.state === 'run'

Try / catch

try {
  await ctx.annotate(msg)
} catch (e) {
  if (/Cannot annotate tests outside/.test(e.message)) return // swallow: too late
  throw e
}

Prevention

When it happens

Trigger: Calling ctx.annotate(...) inside an onTestFailed or onTestFinished handler (which fire after the result state is set); calling annotate inside a microtask that resolves after the test body returns; calling annotate in a beforeEach hook for a test that was already skipped.

Common situations: Moving diagnostic code into afterEach/onTestFailed and forgetting annotate is body-only; awaiting a long promise that crosses the test-finish boundary; annotating from a background timer.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/797c705a520cb0f0. Report an issue: GitHub.

Appendix: source

Thrown at packages/vitest/src/runtime/runner/context.ts:192

  context.task = test

  context.skip = (condition?: boolean | string, note?: string): never => {
    if (condition === false) {
      // do nothing
      return undefined as never
    }
    test.result ??= { state: 'skip' }
    test.result.pending = true
    throw new PendingError(
      'test is skipped; abort execution',
      test,
      typeof condition === 'string' ? condition : note,
    )
  }

  context.annotate = ((message, type, attachment) => {
    if (test.result && test.result.state !== 'run') {
      throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`)
    }

    const annotation: TestAnnotation = {
      message,
      type: typeof type === 'object' || type === undefined ? 'notice' : type,
    }
    const annotationAttachment = typeof type === 'object' ? type : attachment

    if (annotationAttachment) {
      annotation.attachment = annotationAttachment

      manageArtifactAttachment(annotation.attachment)
    }

    return recordAsyncOperation(
      test,
      recordArtifact(test, { type: 'internal:annotation', annotation }).then(async ({ annotation }) => {
        if (!runner.onTestAnnotate) {

View on GitHub (pinned to 1fa9837ec2)