{"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/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runner/run.ts#L334-L370","documentation":"`aroundEach` and `aroundAll` hooks receive a `runTest`/`runSuite` callback that must be called exactly once to proceed with the test/suite. In `callAroundHooks` (run.ts:342-356), the `use` closure tracks a `useCalled` flag; if `use`/`runTest`/`runSuite` is invoked a second time within the same hook, an `AroundHookMultipleCallsError` is thrown.","triggerScenarios":"Calling `runTest()` twice in an `aroundEach` hook; calling `runSuite()` in a loop inside `aroundAll`; awaiting `runTest()` and then calling it again conditionally; an accidental double-call due to a try/finally that calls it in both paths.","commonSituations":"Wrapping runTest in conditional logic where both branches call it; retry logic that re-invokes runTest; misunderstanding that runTest is single-shot.","solutions":["Call `runTest`/`runSuite` exactly once, on the main code path.","Ensure no try/catch/finally branch calls the callback more than once.","If you need retry behavior, do it inside the test, not by re-invoking runTest."],"exampleFix":"// before\naroundEach(async (runTest) => {\n  await setup()\n  await runTest()\n  await runTest() // second call throws\n})\n// after\naroundEach(async (runTest) => {\n  await setup()\n  await runTest()\n  await teardown()\n})","handlingStrategy":"validation","validationCode":"// Guard against double-call in your around hook with a local flag if logic is complex.\naroundEach(async (runTest) => {\n  let called = false\n  const runOnce = async () => {\n    if (called) throw new Error('runTest already called')\n    called = true\n    return runTest()\n  }\n  await setup()\n  await runOnce()\n  await teardown()\n})","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call runTest/runSuite on a single unconditional code path.","Avoid try/finally that could call it twice.","Don't loop over runTest for retries."],"tags":["hooks","aroundeach","aroundall","lifecycle"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}