Cut the residue out of AI-generated patches: find the edits a repair actually needs and drop the rest
Project description
slopcut
Coding agents rarely clean up every failed attempt. When an agent tries three fixes and the third one works, code from the first two can remain in your diff. Your patch becomes the union of every hypothesis the agent tried, while the lines that actually fixed the bug are buried inside it.
slopcut finds those two lines. It reads the session, then repeatedly asks: do the tests still pass without this group of edits?
$ slopcut
session 0f3ab21e-session.jsonl
tests python -m pytest -q
patch 47 lines across 12 edits in 4 attempts
superseded -3 edits (free, never reached the patch)
scratch -1 files (1 test run): repro_bug.py
attempt -5 edits
file -2 edits
result 47 -> 9 lines (81% removed), 2 edits remain
cost 9 test runs (2 cached)
Nothing is written until you say so, and the search happens in a throwaway git worktree, so your working tree is never touched while it runs.
Install
Install it as an isolated command-line tool:
uv tool install slopcut
# or
pipx install slopcut
# or
python -m pip install slopcut
slopcut requires Python 3.10 or newer and Git.
To add the Claude Code command, register this repository as a plugin marketplace:
claude plugin marketplace add ujjwalredd/slopcut
claude plugin install slopcut@slopcut
The plugin calls the installed slopcut executable. After installation, run
/slopcut in Claude Code after a session where the agent edited code.
Use
slopcut # dry run: show what it would remove
slopcut --apply # write the minimized patch (backs up first)
slopcut --level edit # search harder
slopcut --test "cargo test --lib" # if the test command can't be inferred
slopcut --json # machine-readable
By default, slopcut reads the most recent Claude Code session for the current repository, finds the test command the agent ran, and prints a minimized diff.
How hard to search
--level |
removes | test runs | recovery |
|---|---|---|---|
attempt |
whole abandoned attempts | 6.5 | 67% |
hybrid |
attempts, then whole files | 8.4 | 81% |
edit (default) |
individual edits | 10.7 | 100% |
edit is the default because the benchmark below shows that the cheaper levels cost
most of what edit costs while stopping short of the minimum. Use hybrid or
attempt when a single test run is genuinely expensive, such as a slow integration
suite or container rebuild.
--exhaustive repeats each level until nothing more comes out. It almost never
finds anything the single pass missed, so it is off by default.
How it works
- Read the session into attempts. Each attempt is a batch of edits followed by a test run. Everything else, including file reads, searches, and navigation, is dropped because it cannot affect the patch.
- Drop superseded edits. If removing an edit leaves the final tree byte-identical, it was overwritten later and never reached the patch. Pure string work, zero test runs.
- Drop agent-created files. One test run tries to discard files the agent created for debugging, such as reproduction scripts and temporary harnesses.
- Search coarse to fine: attempt, file, then edit. Remove a unit, run the tests, keep the removal only if it passes and the patch got smaller. One success at the top level kills an entire abandoned attempt for the price of a single test run.
Candidates are rebuilt by replaying the surviving edits onto a clean checkout instead of splicing the diff. If an edit depends on something that was removed, the test run can reject that candidate.
Benchmark
Real agent sessions can tell you a patch got 30% smaller, but not whether 30% was all there was. There is no answer key, so "we removed 30%" could mean perfect or hopeless.
So the benchmark builds its own. bench/generate.py writes small libraries with a
seeded bug and a synthetic session that fixes it using common agent behavior: wrong
hypotheses, debug prints, throwaway scripts, dead code, and the real fix buried in
the middle. Because the generator decides which edits are load-bearing, it can score
recovery: of the slop that could be removed, how much did each method find?
700 tasks, 4900 runs, zero errors. Every method judged against the same answer key:
| method | recovery (95% CI) | found the exact minimum | test runs | worst case | broke the build |
|---|---|---|---|---|---|
| no minimization | 0% | 0% | 0 | n/a | 0% |
| keep last attempt only | 59.0% [56.8, 61.1] | 0% | 1 | 1 | 0% |
| delta debugging over hunks | 71.8% [69.2, 74.3] | 20.4% | 3.4 | 12 | 0% |
slopcut --level attempt |
67.3% [66.2, 68.5] | 0% | 6.5 | 11 | 0% |
slopcut --level hybrid |
80.6% [79.1, 82.0] | 12.3% | 8.4 | 16 | 0% |
slopcut --level edit |
100% [100, 100] | 100% | 10.7 | 17 | 0% |
slopcut --level edit --exhaustive |
100% [100, 100] | 100% | 11.2 | 20 | 0% |
slopcut --level edit found the exact minimum on all 700 tasks, and never once
produced a patch that failed its tests.
Everything runs locally in about ten minutes. It needs no containers, API keys, or network access:
pip install -e '.[bench]'
python bench/generate.py --tasks 100 --out bench/out/tasks
python bench/run.py bench/out/tasks --out bench/out/results.jsonl
python bench/report.py bench/out/results.jsonl
python bench/charts.py bench/out/results.jsonl --out docs/charts
What the numbers say
Delta debugging is excellent right up until it isn't. At 3.4 test runs it is by far the cheapest real method, and when the slop sits in its own diff hunks it scores a perfect 100%. When leftover code shares a hunk with the fix, it scores 0%. For example, an agent may add a stray local variable next to the repaired line. Hunk-based reduction cannot remove one without removing the other, so it keeps both.
That is the whole case for reading the session. The two edits are indistinguishable in the final diff and obvious in the history, because the agent made them at different times for different reasons.
"Just keep the last attempt" is a surprisingly good trick, and a dangerous one. One test run buys 59% recovery. But on tasks where the fix calls a helper an earlier attempt introduced, it scores 0%. Throwing away the earlier attempt breaks the repair, so it has to keep everything. Cheap heuristics fail silently on exactly the sessions that are most tangled.
The middle levels do not earn their discount. hybrid spends 8.4 runs to reach
80.6%, where edit spends 10.7 to reach 100%. Two more test runs for the last fifth
of the slop is almost always the right trade, which is why edit is the default.
--exhaustive finds nothing. Identical recovery, +0.5 runs, and a worse tail
(max 20 vs 17). A second pass essentially never finds what the first missed, so it
stays off.
Cost grows gently with patch size. The benchmark used roughly 5 test runs on an 8-line patch and 16 on a 64-line patch. Growth was sublinear because the coarse levels removed abandoned attempts before the per-edit pass started.
What this benchmark does not tell you
The tasks are generated, not scraped from real sessions. That is the trade: a known answer key in exchange for realism. It makes the search measurable because a real-world corpus cannot provide recovery against a known optimum. The mix of slop shapes is still a modelling choice, and how much slop your agent actually produces is not measured here at all.
Generated slop is also cleaner than the real thing: every edit is anchored on text that exists, and none of them interact in surprising ways. Treat 100% recovery as "the search is not the bottleneck," not as a promise about your repository.
What to expect on your own code
The benchmark measures the search, not how much slop your agent produces. Sessions that went straight to the fix have nothing to remove, and slopcut will say so. A patch that was already minimal is a good outcome.
The wins concentrate in the messy sessions: long ones, ones where the agent went down
a wrong path first, ones that left a repro.py behind.
Limitations
- Your tests are the specification. slopcut removes anything they don't cover. Thin tests mean aggressive removal. Read the diff before applying.
- Flaky tests can authorize a bad removal. slopcut checks the baseline before starting, but it cannot catch a test that fails 1 run in 20.
- The smallest unit is one edit. Redundancy inside a single edit is out of reach.
- Reconstruction can drift. slopcut rebuilds the patch from the session. If you hand-edited a file, or a tool no adapter understands touched it, the rebuild is incomplete. slopcut detects this and refuses to apply.
- Adapters: Claude Code, plus SWE-agent and bash-only scaffolds. Adding one is
small: emit
(file, before, after)in order, grouped by test run.
Contributing
See the
contribution guide.
Start with pip install -e '.[dev]' && pytest.
Two numbers decide whether a change to the search is good: recovery must not drop, and test runs must not rise.
Security
slopcut executes test commands on your machine. Inferred commands come from the
selected Claude Code transcript. Only use repositories and session files you trust,
or provide the exact command with --test. Candidate patches are isolated in a Git
worktree, but test processes are not containerized or sandboxed. See
the security policy
for the full trust model and private reporting process.
Releasing
Maintainers can find the release checklist and PyPI Trusted Publishing setup in the release guide.
Credits
The trajectory-minimization idea comes from TRIM: Reducing AI-Generated CodeSlop via Agent Trajectory Minimization (Mathai et al., 2026), which is well worth reading.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slopcut-0.1.0.tar.gz.
File metadata
- Download URL: slopcut-0.1.0.tar.gz
- Upload date:
- Size: 54.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e2ed7e02a5c179c62872b9db470eb914c689b620e216160400a6bfe991f4ad7
|
|
| MD5 |
62a2aa248f4f3ddc048d7933d163f061
|
|
| BLAKE2b-256 |
2649771afc0cb218aac3dbb543a330278466c26143ae0c759290007597fd5337
|
Provenance
The following attestation bundles were made for slopcut-0.1.0.tar.gz:
Publisher:
release.yml on ujjwalredd/slopcut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopcut-0.1.0.tar.gz -
Subject digest:
8e2ed7e02a5c179c62872b9db470eb914c689b620e216160400a6bfe991f4ad7 - Sigstore transparency entry: 2256842243
- Sigstore integration time:
-
Permalink:
ujjwalredd/slopcut@404dd7e8d340db130e13fbd7bc08abe701af8c7b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ujjwalredd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@404dd7e8d340db130e13fbd7bc08abe701af8c7b -
Trigger Event:
push
-
Statement type:
File details
Details for the file slopcut-0.1.0-py3-none-any.whl.
File metadata
- Download URL: slopcut-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a809301f10b81feb30375f7aefe295e0d5bcc9b256ac8482d15442c01019862d
|
|
| MD5 |
8f8bbbf606979c537ac3bb7f1747b0d8
|
|
| BLAKE2b-256 |
990bb9fb1261940a07f2ae6bb7373af46319dcf8b636a727a12ef358e33ed94b
|
Provenance
The following attestation bundles were made for slopcut-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ujjwalredd/slopcut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopcut-0.1.0-py3-none-any.whl -
Subject digest:
a809301f10b81feb30375f7aefe295e0d5bcc9b256ac8482d15442c01019862d - Sigstore transparency entry: 2256842248
- Sigstore integration time:
-
Permalink:
ujjwalredd/slopcut@404dd7e8d340db130e13fbd7bc08abe701af8c7b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ujjwalredd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@404dd7e8d340db130e13fbd7bc08abe701af8c7b -
Trigger Event:
push
-
Statement type: