{"record":{"id":"00dab8fd476d8a72","repo":"jestjs/jest","slug":"ran-this-maxloops-immediates-and-there-are-st","errorCode":null,"errorMessage":"Ran ${this._maxLoops} immediates, and there are still more! Assuming we've hit an infinite recursion and bailing out...","messagePattern":"Ran (.+?) immediates, and there are still more! Assuming we've hit an infinite recursion and bailing out\\.\\.\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-fake-timers/src/legacyFakeTimers.ts","lineNumber":190,"sourceCode":"          \"Assuming we've hit an infinite recursion and bailing out...\",\n      );\n    }\n  }\n\n  runAllImmediates(): void {\n    this._checkFakeTimers();\n    // Only run a generous number of immediates and then bail.\n    let i;\n    for (i = 0; i < this._maxLoops; i++) {\n      const immediate = this._immediates.shift();\n      if (immediate === undefined) {\n        break;\n      }\n      this._runImmediate(immediate);\n    }\n\n    if (i === this._maxLoops) {\n      throw new Error(\n        `Ran ${this._maxLoops} immediates, and there are still more! Assuming ` +\n          \"we've hit an infinite recursion and bailing out...\",\n      );\n    }\n  }\n\n  private _runImmediate(immediate: Tick) {\n    try {\n      immediate.callback();\n    } finally {\n      this._fakeClearImmediate(immediate.uuid);\n    }\n  }\n\n  runAllTimers(): void {\n    this._checkFakeTimers();\n    this.runAllTicks();\n    this.runAllImmediates();","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/jestjs/jest/blob/8e6d128e4a278059ecddecaa97400b04c8ae5fd9/packages/jest-fake-timers/src/legacyFakeTimers.ts#L172-L208","documentation":"Thrown by legacy fake timers' `runAllImmediates()` after exhausting `_maxLoops` iterations (default 100,000). The method drains `setImmediate` callbacks; if each immediate schedules another, the queue never drains and Jest aborts to prevent an infinite recursion.","triggerScenarios":"Using legacy fake timers and calling `jest.runAllTimers()` (which internally calls `runAllImmediates()`) when code recursively calls `setImmediate` within an immediate callback without a break condition.","commonSituations":"Application code using `setImmediate` for a scheduling loop (e.g. a work queue that keeps re-enqueuing). A streaming data consumer that calls setImmediate to yield but the stream never ends in the test.","solutions":["Switch to modern fake timers (`jest.useFakeTimers()`) which give finer-grained control.","Use `jest.runOnlyPendingTimers()` to drain only the current batch without recursing into newly scheduled ones.","Fix the recursive `setImmediate` in application code to terminate.","Mock the source of the recurring scheduling in the test."],"exampleFix":"// before\njest.useFakeTimers({ legacyFakeTimers: true });\nfunction work() { setImmediate(work); }\nwork();\njest.runAllTimers(); // throws via runAllImmediates\n\n// after\njest.useFakeTimers();\nwork();\njest.runOnlyPendingTimers(); // drains only current batch","handlingStrategy":"validation","validationCode":"// Check for active setImmediate loops before runAllTimers\nfunction hasActiveImmediates(fakeTimers) {\n  return fakeTimers.getTimerCount() > 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n  jest.runAllTimers();\n} catch (e) {\n  if (e.message.includes('infinite recursion')) {\n    jest.runOnlyPendingTimers(); // fallback\n  } else { throw e; }\n}","preventionTips":["Use modern fake timers to avoid the legacy runAllImmediates path entirely.","Use runOnlyPendingTimers for code with recursive setImmediate.","Clear all immediates with clearAllTimers before re-running if needed."],"tags":["jest-fake-timers","legacy-fake-timers","infinite-loop","setimmediate"],"backgroundTag":null,"analyzedSha":"8e6d128e4a278059ecddecaa97400b04c8ae5fd9","analyzedAt":"2026-08-10T18:11:27.960Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}