Mutation testing, minus the pain
Break your tests before prod does.
BugBait plants realistic bugs in your code, runs your test suite against each one, and writes verified tests for every gap it finds. Your code is never touched — it all happens on throwaway copies.
Free and open source. Your code never leaves your machine.
- languages
- 10
- bug kinds
- 9
- lines of your code touched
- 0
$ python3 main.py ~/code/my-project --bugs 2
Baseline: 4 tests pass
Bug 1 SURVIVED age >= 18 → age > 18 [boundary, 5/5]
Bug 2 SURVIVED or → and [operator]
Score: 0/2 bugs caught (0%)
Fixer is writing tests for 2 survivors...
[+] verified test added boundary at 18
[+] verified test added broken validation
Score: 0% → 100% (0/2 → 2/2 bugs caught)
Three steps to trustworthy tests
-
The Saboteur plants bugs
An AI agent studies your code and plants small, realistic mistakes — off-by-ones, flipped operators, dropped guards. Each gets a realism rating; artificial ones are thrown out.
-
The sandbox scores your tests
Every bug runs against your full suite in its own throwaway copy. Caught means a test failed; survived means a real gap. Your score is simply caught ÷ planted.
-
The Fixer writes the missing tests
A second agent writes a test per survivor — each verified to pass on your code and fail on the bug. Add them to your project or open a pull request.
Meet the bugs
Nine bug kinds, each a one-line change that keeps the code valid — the kind of thing that survives code review. Pick your language:
| Kind | Python | JavaScript | TypeScript | Go | Java | Rust | Ruby | PHP | C# | Kotlin |
|---|---|---|---|---|---|---|---|---|---|---|
| off-by-one | range(n) → range(n + 1) | i < len → i <= len | i < len → i <= len | i < n → i <= n | i < n → i <= n | i < n → i <= n | (0...n) → (0..n) | $i < $n → $i <= $n | i < n → i <= n | i < n → i <= n |
| boundary | age >= 18 → age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 | $age >= 18 → $age > 18 | age >= 18 → age > 18 | age >= 18 → age > 18 |
| wrong operator | and → or | || → && | || → && | && → || | && → || | && → || | && → || | $a && $b → $a || $b | && → || | && → || |
| inverted condition | if valid: → if not valid: | if (ok) → if (!ok) | if (ok) → if (!ok) | if (ok) → if (!ok) | if (ok) → if (!ok) | if ok → if !ok | if ok → unless ok | if ($ok) → if (!$ok) | if (ok) → if (!ok) | if (ok) → if (!ok) |
| wrong default | n=10 → n=0 | n = 10 → n = 0 | n = 10 → n = 0 | n := 10 → n := 0 | n = 10 → n = 0 | let n = 10 → let n = 0 | n = 10 → n = 0 | $n = 10 → $n = 0 | n = 10 → n = 0 | n = 10 → n = 0 |
| swapped arguments | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) | f($a, $b) → f($b, $a) | f(a, b) → f(b, a) | f(a, b) → f(b, a) |
| missing case | a guard clause removed | |||||||||
| wrong value | timeout=30 → timeout=300 | timeout: 30 → timeout: 300 | timeout: 30 → timeout: 300 | timeout := 30 → timeout := 300 | timeout = 30 → timeout = 300 | timeout = 30 → timeout = 300 | timeout = 30 → timeout = 300 | $timeout = 30 → $timeout = 300 | timeout = 30 → timeout = 300 | timeout = 30 → timeout = 300 |
| error handling | except ValueError → except Exception | throw … → return null | throw … → return null | if err != nil check dropped | catch (IOException) → catch (Exception) | a Result check dropped | rescue ArgumentError → rescue StandardError | catch (ValueError) → catch (Exception) | catch (IOException) → catch (Exception) | catch (IOException) → catch (Exception) |
Speaks your language
- Pythonpytest / unittest
- JavaScriptnode --test · vitest
- TypeScriptvitest · no build step
- Gogo test
- JavaMaven & Gradle
- Rustcargo test
- Rubyminitest & RSpec
- PHPPHPUnit
- C#dotnet test
- Kotlinkotlinc + JUnit
Two ways to run it: the dashboard (point-and-click, pick repos after connecting GitHub) or the command line (scriptable, with merge gates and a pre-commit hook).
Running in two minutes
Clone it and install the four dependencies:
git clone https://github.com/AVI2907/bugbait.git cd bugbait && pip install -r requirements.txtAdd your model key to
.env:META_API_KEY=your_key_hereRun it on your own project:
python3 main.py ~/code/my-projectThen open the dashboard and test anything:
python3 app.py # opens http://localhost:5050
Your code stays on your machine. Nothing is pushed anywhere unless you click “Open pull request”.