{"id":"2deacf699ecb7128","repo":"vitest-dev/vitest","slug":"hook-name-can-only-be-called-inside-a-test","errorCode":null,"errorMessage":"Hook ${name}() can only be called inside a test","messagePattern":"Hook (.+?)\\(\\) can only be called inside a test","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/hooks.ts","lineNumber":454,"sourceCode":"}\n\nexport function getAroundHookStackTrace(hook: Function): Error | undefined {\n  return AROUND_STACK_TRACE_KEY in hook && hook[AROUND_STACK_TRACE_KEY] instanceof Error\n    ? hook[AROUND_STACK_TRACE_KEY]\n    : undefined\n}\n\nfunction createTestHook<T>(\n  name: string,\n  handler: (test: TaskPopulated, handler: T, timeout?: number) => void,\n): TaskHook<T> {\n  return (fn: T, timeout?: number) => {\n    assertTypes(fn, `\"${name}\" callback`, ['function'])\n\n    const current = getCurrentTest()\n\n    if (!current) {\n      throw new Error(`Hook ${name}() can only be called inside a test`)\n    }\n\n    return handler(current, fn, timeout)\n  }\n}\n","sourceCodeStart":436,"sourceCodeEnd":460,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/hooks.ts#L436-L460","documentation":"`onTestFailed` and `onTestFinished` are test-scoped hooks created via `createTestHook` (hooks.ts:444). They require an active test (`getCurrentTest()` must be truthy) at call time. If called outside any test body — at the top level, inside `describe`, inside `beforeAll`/`beforeEach`, or after the test has finished — there is no current test to attach the hook to, so the error is thrown with the hook name.","triggerScenarios":"Calling `onTestFailed(fn)` at module top level; calling it inside a `describe` factory body directly (not inside a test); calling `onTestFinished` inside `beforeEach`/`beforeAll` suite hooks; calling after the current test already completed.","commonSituations":"Placing cleanup handlers at the wrong nesting level; copy-pasting a hook out of a test; refactoring that moves a hook call into a suite-level setup.","solutions":["Move the `onTestFailed`/`onTestFinished` call inside a `test(...)` body.","For suite-level setup/teardown, use `beforeAll`/`afterAll` or `beforeEach`/`afterEach` instead.","Ensure the hook is registered synchronously while the test is the current task."],"exampleFix":"// before\ndescribe('suite', () => {\n  onTestFailed(() => { /* wrong place */ })\n  test('x', () => {})\n})\n// after\ndescribe('suite', () => {\n  test('x', () => {\n    onTestFailed(() => { /* correct */ })\n  })\n})","handlingStrategy":"validation","validationCode":"// Structural check: ensure onTestFailed/onTestFinished calls are lexically inside a test.\n// In practice, rely on the runtime check; statically, forbid these calls in hooks via lint:\n// 'no-restricted-syntax' banning onTestFailed/onTestFinished inside beforeEach/afterEach/beforeAll/afterAll blocks.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always place onTestFailed/onTestFinished directly inside a test() body.","Use afterEach/afterAll for cleanup that must run regardless.","Avoid sharing helpers that register test-scoped hooks."],"tags":["hooks","lifecycle","ontestfailed","ontestfinished","test-context"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}