Injection Modes
FlakeMonster supports three injection densities — light, medium, and hardcore — that control how many delay statements are injected into your code.
Overview
Injection modes control the density of await __FlakeMonster__(N) delay statements inserted into your async code. Each injected delay forces a pause of N milliseconds (determined by the seed), giving other async operations a chance to interleave and exposing timing-dependent bugs that hide in fast environments.
The tradeoff is straightforward: more delays means more chaos, which means a higher chance of surfacing flaky tests — but it also means slower test runs. FlakeMonster gives you three density levels to balance coverage against speed.
Set the mode with the --mode flag or the mode config key:
$ npx flake-monster test --mode hardcore --cmd "npm test"
Light Mode
Light mode injects the fewest delays possible while still perturbing async timing:
- One delay at the top of each async function body — before the first statement
- One delay at the first module-level statement (if top-level await is in use)
This is the fastest mode. It nudges the timing of every async entry point without slowing down internal logic. Best for quick sanity checks and fast feedback loops during development.
Before injection
async function loadUser(id) { const user = await api.getUser(id); const prefs = await api.getPrefs(id); return { ...user, ...prefs }; }
After injection (light)
import { __FlakeMonster__ } from "./flake-monster.runtime.js"; async function loadUser(id) { /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(23); const user = await api.getUser(id); const prefs = await api.getPrefs(id); return { ...user, ...prefs }; }
Only one delay is inserted — at the very top of the function, before the first statement. The return and all other statements are left untouched.
Medium Mode
Medium mode is the default. It injects a delay before every statement in async function bodies and at the module top level, with two exceptions:
- Delays between statements in async functions
- Delays between module-level statements (top-level await)
- Skips
returnandthrowstatements — no delay is inserted before areturnorthrow
This is the sweet spot for most workflows: enough chaos to catch real flakes without making test runs prohibitively slow. Best for standard CI gates and PR flake detection.
After injection (medium)
import { __FlakeMonster__ } from "./flake-monster.runtime.js"; async function loadUser(id) { /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(23); const user = await api.getUser(id); /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(41); const prefs = await api.getPrefs(id); return { ...user, ...prefs }; }
Delays appear before the const user and const prefs statements. No delay is inserted before return because medium mode skips return and throw statements. This ensures the function always completes promptly after its last real operation.
Hardcore Mode
Hardcore mode injects a delay before every single statement — no exceptions. Every return, every throw, every assignment gets a delay in front of it.
- Delays before every statement in async functions, including
returnandthrow - Delays before every module-level statement
- Maximum chaos, maximum detection surface
This is the most thorough mode. It creates the highest probability of exposing timing-dependent failures. Best for deep debugging, stress testing, and nightly builds where runtime cost is acceptable.
After injection (hardcore)
import { __FlakeMonster__ } from "./flake-monster.runtime.js"; async function loadUser(id) { /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(23); const user = await api.getUser(id); /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(41); const prefs = await api.getPrefs(id); /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(7); return { ...user, ...prefs }; }
Every statement now has a delay in front of it, including the return. This maximizes the chance that interleaved async operations will expose ordering bugs — even ones that depend on how fast a function returns.
Choosing a Mode
The right mode depends on where you are in your workflow and how much time you can spend:
| Use Case | Recommended Mode |
|---|---|
| Quick sanity check | light |
| Standard CI gate | medium |
| PR flake detection | medium |
| Deep debugging | hardcore |
| Nightly build | hardcore |
| Reproducing a specific failure | medium or hardcore |
Progressive approach: Start with light to get fast initial coverage. If no flakes appear, escalate to medium. Reserve hardcore for when you suspect deep timing issues or want maximum confidence before a release.
# Quick check during development $ npx flake-monster test --mode light --runs 5 --cmd "npm test" # Standard CI gate $ npx flake-monster test --mode medium --runs 10 --cmd "npm test" # Nightly deep scan $ npx flake-monster test --mode hardcore --runs 25 --cmd "npm test"
Additional Controls
Beyond the mode setting, FlakeMonster provides two flags that fine-tune where delays are injected. These work with any mode.
skipTryCatch
A config-file-only option reserved for a future release. When implemented, setting this to true will prevent FlakeMonster from injecting delays inside try, catch, or finally blocks. This will be useful when injected delays cause timeout exceptions or interfere with error-handling logic that is sensitive to execution timing.
Default: false. This option does not currently affect injection behavior.
// Via config file (flake-monster.config.json) { "skipTryCatch": true, "mode": "medium" }
Example: with skipTryCatch: true, delays are omitted inside the try block:
async function fetchData() { /* @flake-monster[jt92-se2j!] v1 */ await __FlakeMonster__(18); const url = buildUrl(); try { // No delays injected here when skipTryCatch is true const res = await fetch(url); return await res.json(); } catch (err) { // No delays here either return null; } }
skipGenerators
When set to true (the default), FlakeMonster skips async generator functions (async function*) entirely. Generator functions yield values incrementally, and injected delays between yields can cause unexpected iterator behavior or deadlocks in consumer code.
Default: true (async generators are skipped).
// To opt in to generator injection, set skipGenerators to false { "skipGenerators": false, "mode": "hardcore" }
With skipGenerators: true (the default), this function is left completely untouched:
// Skipped — no delays injected into async generators by default async function* streamEvents(source) { for await (const event of source) { yield transform(event); } }
Tip: If you are specifically hunting for flakes in generator-based streaming code, set
skipGenerators: falseand usehardcoremode. Be prepared for longer test runs.