{"record":{"id":"3ad5a6cdfd46e937","repo":"vitest-dev/vitest","slug":"the-callbackname-callback-was-called-multiple","errorCode":null,"errorMessage":"The `${callbackName}` callback was called multiple times in the `${hookName}` hook. The callback can only be called once per hook.","messagePattern":"The `(.+?)` callback was called multiple times in the `(.+?)` hook\\. The callback can only be called once per hook\\.","errorType":"exception","errorClass":"AroundHookMultipleCallsError","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/run.ts","lineNumber":352,"sourceCode":"    // Promise that resolves when hook completes\n    let resolveHookComplete!: () => void\n    let rejectHookComplete!: (error: Error) => void\n    const hookCompletePromise = new Promise<void>((resolve, reject) => {\n      resolveHookComplete = resolve\n      rejectHookComplete = reject\n    })\n\n    const use = async () => {\n      // shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.\n      if (setupTimeout.isTimedOut()) {\n        // we can throw any error to bail out.\n        // this error is not seen by end users since `runNextHook` already rejected with timeout error\n        // and this error is caught by `rejectHookComplete`.\n        throw new Error('__VITEST_INTERNAL_AROUND_HOOK_ABORT__')\n      }\n\n      if (useCalled) {\n        throw new AroundHookMultipleCallsError(\n          `The \\`${callbackName}\\` callback was called multiple times in the \\`${hookName}\\` hook. `\n          + `The callback can only be called once per hook.`,\n        )\n      }\n      useCalled = true\n      resolveUseCalled()\n\n      // Setup phase completed - clear setup timer\n      setupTimeout.clear()\n      setupLimitConcurrencyRelease?.()\n\n      // Run inner hooks - don't time this against our teardown timeout\n      await runNextHook(index + 1).catch(e => hookErrors.push(e))\n\n      teardownLimitConcurrencyRelease = await limitMaxConcurrency.acquire()\n\n      // Start teardown timer after inner hooks complete - only times this hook's teardown code\n      teardownTimeout = createTimeoutPromise(timeout, 'teardown', stackTraceError)","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/run.ts#L334-L370","documentation":"AroundHookMultipleCallsError thrown when the aroundEach/aroundAll callback (runTest/use) is invoked more than once in a single hook invocation. Around hooks wrap the test/suite exactly once: the first call runs the inner content, and a second call would re-enter already-run hooks. Vitest tracks a useCalled flag and raises this on the second invocation.","triggerScenarios":"Calling `runTest()` twice in the same aroundEach body; calling runTest in a loop; calling runTest from both a try and a finally block; calling runTest after an early await resolved.","commonSituations":"Copy-paste error; retry logic mistakenly wrapping runTest; conditional that calls runTest in two branches without early return.","solutions":["Ensure runTest is called exactly once along every code path through the hook.","Restructure retry logic to live inside the test body, not by re-invoking runTest.","If you need setup+teardown without wrapping, use beforeEach/afterEach instead of aroundEach."],"exampleFix":"// before\naroundEach(async (runTest) => {\n  await runTest()\n  await runTest() // throws\n})\n\n// after\naroundEach(async (runTest) => {\n  await setup()\n  await runTest()\n  await teardown()\n})","handlingStrategy":"validation","validationCode":"function aroundWrapper(setup: () => Promise<void>, teardown: () => Promise<void>) {\n  return async (runTest: () => Promise<void>) => {\n    await setup()\n    await runTest() // called exactly once\n    await teardown()\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runWithAroundHook()\n} catch (e) {\n  if (e instanceof AroundHookMultipleCallsError) {\n    // audit hook for double runTest invocation\n  } else throw e\n}","preventionTips":["Call runTest exactly once per aroundEach/aroundAll body.","Avoid loops or branches that could invoke runTest twice.","Use beforeEach/afterEach when you do not need to wrap the test."],"tags":["hooks","around-hook","lifecycle"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}