{"id":"e697a1e872c975a5","repo":"vitest-dev/vitest","slug":"cannot-call-ontestfinished-inside-a-test-hook","errorCode":null,"errorMessage":"Cannot call \"onTestFinished\" inside a test hook.","messagePattern":"Cannot call \"onTestFinished\" inside a test hook\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/run.ts","lineNumber":142,"sourceCode":"  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      }\n      catch (e) {\n        failTask(test.result!, e, runner.config._diffOptions)\n      }","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/run.ts#L124-L160","documentation":"Symmetric to error 347, during hook execution Vitest replaces `context.onTestFinished` with a throwing stub (run.ts:141-143). Registering an onTestFinished handler from within `beforeEach`/`afterEach` is disallowed because the registration window for that test has effectively closed.","triggerScenarios":"Calling `context.onTestFinished(fn)` or the global `onTestFinished(fn)` from inside a `beforeEach`/`afterEach` hook.","commonSituations":"Porting onTestFinished-based cleanup into a shared hook; helpers that wrap both setup and teardown registration.","solutions":["Register `onTestFinished` inside the test body, not in hooks.","Use `afterEach` for guaranteed post-test cleanup instead of onTestFinished.","Restructure shared setup helpers to return cleanup callbacks the caller invokes."],"exampleFix":"// before\nbeforeEach(({ onTestFinished }) => {\n  const db = connect()\n  onTestFinished(() => db.close()) // throws\n})\n// after\nafterEach(() => {\n  closeDbIfOpen()\n})","handlingStrategy":"validation","validationCode":"// Ensure no beforeEach/afterEach body references onTestFinished.\n// Grep: rg 'onTestFinished' within hook callbacks; move logic to afterEach.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never reference onTestFinished inside beforeEach/afterEach.","Use afterEach for guaranteed teardown.","Register onTestFinished inside the test body only."],"tags":["hooks","ontestfinished","lifecycle","test-context"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}