{"id":"1e7573a3db72fcca","repo":"vitest-dev/vitest","slug":"cannot-call-ontestfailed-inside-a-test-hook","errorCode":null,"errorMessage":"Cannot call \"onTestFailed\" inside a test hook.","messagePattern":"Cannot call \"onTestFailed\" inside a test hook\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/run.ts","lineNumber":139,"sourceCode":"  runner: VitestRunner,\n  test: Test,\n  hooks: ((context: TestContext) => Awaitable<void>)[],\n  sequence: SequenceHooks,\n) {\n  if (sequence === 'stack') {\n    hooks = hooks.slice().reverse()\n  }\n\n  if (!hooks.length) {\n    return\n  }\n\n  const context = test.context as WriteableTestContext\n\n  const onTestFailed = test.context.onTestFailed\n  const onTestFinished = test.context.onTestFinished\n  context.onTestFailed = () => {\n    throw new Error(`Cannot call \"onTestFailed\" inside a test hook.`)\n  }\n  context.onTestFinished = () => {\n    throw new Error(`Cannot call \"onTestFinished\" inside a test hook.`)\n  }\n\n  if (sequence === 'parallel') {\n    try {\n      await Promise.all(hooks.map(fn => limitMaxConcurrency(() => fn(test.context))))\n    }\n    catch (e) {\n      failTask(test.result!, e, runner.config._diffOptions)\n    }\n  }\n  else {\n    for (const fn of hooks) {\n      try {\n        await limitMaxConcurrency(() => fn(test.context))\n      }","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/run.ts#L121-L157","documentation":"While `beforeEach`/`afterEach` (and onFinished/onFailed) hooks execute, Vitest temporarily replaces `context.onTestFailed` with a function that throws this error (run.ts:138-140). This prevents registering a failure handler for an already-finishing test from within a hook, where the registration semantics are ambiguous.","triggerScenarios":"Calling `context.onTestFailed(fn)` (or `onTestFailed(fn)` where globals resolve to the context's method) from inside a `beforeEach` or `afterEach` hook body.","commonSituations":"Sharing setup logic that registers failure handlers; refactoring test helpers into hooks without realizing they call onTestFailed.","solutions":["Register `onTestFailed` inside the test body itself, not inside hooks.","If you need conditional failure handling per test, pass a flag and register the handler in each test.","Use `afterEach` for cleanup that must run regardless, instead of onTestFailed."],"exampleFix":"// before\nbeforeEach(({ onTestFailed }) => {\n  onTestFailed(() => cleanup()) // throws\n})\n// after\nafterEach(() => {\n  cleanup()\n})","handlingStrategy":"validation","validationCode":"// Cannot call onTestFailed from a hook by design; the guard is structural.\n// Ensure no beforeEach/afterEach body references onTestFailed.\n// Grep your suite: rg 'onTestFailed' inside hook definitions.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never reference onTestFailed inside beforeEach/afterEach.","Use afterEach for cleanup that previously lived in onTestFailed.","Register failure handlers inside the test body."],"tags":["hooks","ontestfailed","lifecycle","test-context"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}