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
- Move the annotate call inside the test body, before it returns.
- If annotating on failure, attach the diagnostic to the error itself (via expect.assertions or a custom assertion) rather than calling annotate from onTestFailed.
- 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
- Call annotate only inside the test body.
- Do not annotate from onTestFailed/onTestFinished or background tasks.
- Await all work before the test body returns.
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
- Cannot call "onTestFailed" inside a test hook.
- Cannot call "onTestFinished" inside a test hook.
- expect.soft() can only be used inside a test
- Hook () can only be called inside a test
- Test attachment requires only one of "body" or "path" to be…
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)