Slopbouncer sends a coding agent back when it weakens a test.
A coding agent's job is to make the checks pass. One way is to weaken the check. When an agent is about to finish and has weakened an existing test, Slopbouncer sends it back once: put the test back and make the code pass it, or leave the change and tell the person which behaviour changed.
It reads code and never runs it, so it takes a fraction of a second.
Setting up your agent
$ gem install slopbouncer $ slopbouncer install claude-code
It needs Ruby 3.2 or later and git. Slopbouncer is most use when the agent that made the change hears about it. The agent knows why it changed a test, and it can put the test back. One command sets an agent up:
- Claude Code
slopbouncer install claude-code - Cursor
slopbouncer install cursor - Gemini CLI
slopbouncer install gemini - Codex
slopbouncer install codex
Claude Code is the one it has been run under so far. The Cursor, Gemini CLI and Codex hooks are written to those agents' published hook contracts and have not yet been run inside the agents themselves.
What the agent is told
Slopbouncer: this change weakens 2 existing tests. test/checkout_test.rb: Checkout lets a guest pay: assertion_removed: assert_equal 'paid', order.status test/refund_test.rb: Refund refunds part of an order: expected_value_changed: 100 → 90 For each one, do one of two things before you finish: 1. Put the test back as it was, and make the code pass it. 2. Leave the change, and tell the person which behaviour changed and why the test had to change with it.
What it flags
- An assertion removed from an existing test
- An expected value edited
- A test deleted, skipped or marked pending
- A test left with no assertions at all
What it leaves alone
- New tests added with new code (that is what test-driven development looks like)
- Tests that gained assertions
- Renamed or moved tests whose checks are unchanged
Running it yourself and in a pipeline
An agent set on a passing build can still get round a hook, so keep running the plain command where a person will see it. In a pipeline, or before a push, it is the backstop to the hook.
$ slopbouncer main..HEAD test/basket_test.rb Basket applies a voucher skip_added marked to be skipped test/checkout_test.rb Checkout lets a guest pay assertion_removed assert_equal "paid", order.status test/refund_test.rb Refund refunds part of an order expected_value_changed 100 → 90 3 tests got weaker in 3 files
Its limits
Slopbouncer catches tests that are visibly weaker. A test that looks thorough and checks nothing needs mutation testing, which is slower and runs the code. Use both: Slopbouncer on every change, mutation testing behind it.
It flags changes for a person to review. It does not block a merge unless you ask it to.