BugBait GitHub

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
bugbait — my-project
$ 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

  1. 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.

  2. 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.

  3. 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:

KindPythonJavaScriptTypeScriptGoJavaRustRubyPHPC#Kotlin
off-by-onerange(n) → range(n + 1)i < len → i <= leni < len → i <= leni < n → i <= ni < n → i <= ni < n → i <= n(0...n) → (0..n)$i < $n → $i <= $ni < n → i <= ni < n → i <= n
boundaryage >= 18 → age > 18age >= 18 → age > 18age >= 18 → age > 18age >= 18 → age > 18age >= 18 → age > 18age >= 18 → age > 18age >= 18 → age > 18$age >= 18 → $age > 18age >= 18 → age > 18age >= 18 → age > 18
wrong operatorand → or|| → &&|| → &&&& → ||&& → ||&& → ||&& → ||$a && $b → $a || $b&& → ||&& → ||
inverted conditionif valid: → if not valid:if (ok) → if (!ok)if (ok) → if (!ok)if (ok) → if (!ok)if (ok) → if (!ok)if ok → if !okif ok → unless okif ($ok) → if (!$ok)if (ok) → if (!ok)if (ok) → if (!ok)
wrong defaultn=10 → n=0n = 10 → n = 0n = 10 → n = 0n := 10 → n := 0n = 10 → n = 0let n = 10 → let n = 0n = 10 → n = 0$n = 10 → $n = 0n = 10 → n = 0n = 10 → n = 0
swapped argumentsf(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 casea guard clause removed
wrong valuetimeout=30 → timeout=300timeout: 30 → timeout: 300timeout: 30 → timeout: 300timeout := 30 → timeout := 300timeout = 30 → timeout = 300timeout = 30 → timeout = 300timeout = 30 → timeout = 300$timeout = 30 → $timeout = 300timeout = 30 → timeout = 300timeout = 30 → timeout = 300
error handlingexcept ValueError → except Exceptionthrow … → return nullthrow … → return nullif err != nil check droppedcatch (IOException) → catch (Exception)a Result check droppedrescue ArgumentError → rescue StandardErrorcatch (ValueError) → catch (Exception)catch (IOException) → catch (Exception)catch (IOException) → catch (Exception)

Speaks your language

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

  1. Clone it and install the four dependencies:

    git clone https://github.com/AVI2907/bugbait.git
    cd bugbait && pip install -r requirements.txt
  2. Add your model key to .env:

    META_API_KEY=your_key_here
  3. Run it on your own project:

    python3 main.py ~/code/my-project
  4. Then 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”.