An OSS patch needs two frozen artifacts before model review. The first artifact is a git bisect SHA. The second is a snapshot of public names.
Models that skip those bounds invent extra files. They also invent extra public APIs without cause. Review then drifts away from the real bug.
This workflow is for maintainers who already reproduced the bug. It does not replace a real failing test. It only stops a model from widening scope.
The usual failure
A volunteer pastes an issue into a coding model. The model returns a large and confident diff. The diff often touches helpers the crash never reached.
It also adds a new public function for clarity. Reviewers then argue about style instead of behavior. The merge still ships a silent API expansion.
Downstream packages break on the next minor tag. The original crash may even stay present. The repository now owns two problems, not one.
Two artifacts that bound the work
Keep both artifacts in the issue thread. Treat both files as hard merge gates. A model may edit only inside those gates.
The first gate is the FIRST_BAD commit file. That file stores the first bad commit. The second gate file is api.snap.bad with public names.
The recipe below uses a small Python package layout. Commands are a local proposal, not a published benchmark. They belong on a throwaway clone only.
1. Pin the tree without a model
The maintainer clones the project at the reporter's tag. A chat session does not start yet. The exact ref is recorded in the issue.
git clone https://github.com/example/lib.git
cd lib
git switch --detach v2.4.1
python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
pytest tests/test_parse.py::test_empty_header -q
The selected test must fail on that detached ref. A passing test means the bug is elsewhere. Work stops there, with no model session.
2. Bisect until one SHA remains
The failing test becomes the bisect run command. Logs are not pasted into a model here. The SHA becomes the later review window.
git bisect start
git bisect bad HEAD
git bisect good v2.3.0
git bisect run pytest tests/test_parse.py::test_empty_header -q
git bisect log > bisect.log
BAD=$(git rev-parse refs/bisect/bad)
echo "$BAD" > FIRST_BAD
git bisect reset
git switch --detach "$(cat FIRST_BAD)"
FIRST_BAD now names the commit that introduced failure. Files from that commit and its parent are listed. That list is the only allowed edit set.
git diff --name-only "$(cat FIRST_BAD)^" "$(cat FIRST_BAD)" > allowed_files.txt
cat allowed_files.txt
A flaky bisect run stops the workflow. Flaky tests will poison every later bound. The test harness is fixed before any patch.
3. Snapshot public names on the bad tree
Public names are the hidden merge risk. A model often exports a helper as the fix. The surface is captured before any human edit.
The script below is an unexecuted recipe. It walks AST nodes for top-level definitions. The glob is adjusted to match the real package.
# tools/api_snapshot.py
"""Write sorted top-level public names. Recipe only."""
from pathlib import Path
import ast
import sys
root = Path(sys.argv[1])
names = []
for path in sorted(root.rglob("*.py")):
if "test" in path.parts:
continue
tree = ast.parse(path.read_text(encoding="utf-8"))
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)):
if not node.name.startswith("_"):
names.append(f"{path.as_posix()}:{node.name}")
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id.isupper():
names.append(f"{path.as_posix()}:{t.id}")
Path("api.snap").write_text("\n".join(names) + "\n", encoding="utf-8")
print(f"wrote {len(names)} public names")
python tools/api_snapshot.py src/lib
cp api.snap api.snap.bad
wc -l api.snap.bad
api.snap.bad is stored beside the FIRST_BAD file. Those two files form the review contract. Later diffs must keep the snapshot byte identical.
4. Write a patch contract the model must obey
The bounds go into a small YAML file. That file is attached to the review prompt. The contract is the prompt, not the issue text.
# patch_contract.yaml
first_bad: "REPLACE_WITH_SHA"
allowed_files:
- src/lib/parse.py
- tests/test_parse.py
forbidden:
- new public names
- new modules
- dependency bumps
- formatter-only rewrites
required_tests:
- pytest tests/test_parse.py::test_empty_header
- pytest tests/test_parse.py
api_snapshot: api.snap.bad
allowed_files comes from the bisect file list. A model does not extend that list. New tests may import existing public names only.
A tiny checker can fail the review early. It is also a recipe, not a product. It runs before and after the patch.
# tools/check_contract.sh
set -euo pipefail
test -f FIRST_BAD
test -f api.snap.bad
test -f allowed_files.txt
git diff --name-only | while read -r f; do
grep -Fxq "$f" allowed_files.txt || {
echo "out of bounds: $f"
exit 1
}
done
python tools/api_snapshot.py src/lib
diff -u api.snap.bad api.snap
5. Patch only inside the window
Listed files are edited and nothing else. New helpers stay private with a leading underscore. The public snapshot is re-run after each edit.
python tools/api_snapshot.py src/lib
diff -u api.snap.bad api.snap
An empty snapshot diff is the surface pass. A new public name fails review immediately. Tests that pass do not excuse an API expansion.
The original test and its nearest file are run next.
pytest tests/test_parse.py::test_empty_header -q
pytest tests/test_parse.py -q
The first command must pass after the patch. The second command must remain fully green. A dead sibling test means leaked state.
Both test commands are recorded in the issue. They are not treated as the only merge rule. They only prove the window still holds.
6. Call a model only after the contract exists
A coding model can read the allowed files. It cannot choose the git bisect window. The SHA, file list, snapshot, and failing test are fed in.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. Those options can host this review loop without a local GPU.
They still cannot replace bisect or a frozen API snapshot. A short prompt is used for the review. The model must return a unified diff only.
You may edit only these files:
{allowed_files}
Do not add public functions, classes, or constants.
Public names must match api.snap.bad exactly.
Failing test:
pytest tests/test_parse.py::test_empty_header
Return a unified diff and nothing else.
The diff is applied on a clean worktree. The check_contract script is run at once. The patch is dropped if the checker prints any path.
7. Gate license headers and version policy
OSS merges fail on paperwork as often as logic. Every touched file is scanned for a license header. The changelog must match the stated version policy.
while read -r f; do
grep -q "SPDX-License-Identifier" "$f" || echo "missing SPDX: $f"
done < allowed_files.txt
SemVer treats a new public name as a minor bump. This workflow forbids that bump on a bugfix branch. Intentional API work moves to a separate issue.
A model does not rewrite CHANGELOG tone. One factual bullet is inserted for the crash. The rest of the file stays untouched.
Limitations
Bisect lies when tests are order dependent. It also lies when fixtures need production data. The AST snapshot also misses all re-exports.
It also misses C extensions and generated protobuf stubs. Private names can still break subclasses and plugins. Comment-only edits can still destroy git blame.
Models still invent imports and dead branches. The contract bounds files and public names only. It does not prove races or performance.
Free model access and a free server are availability claims. This article does not state quotas, model names, or hardware. Verify current terms on the product site before use.
Who should skip this method
Do not use this method under a security embargo. Do not use it for private CVE patches. Do not use it on generated vendor trees.
New contributors who cannot run bisect should wait. Header-only libraries need a different surface snapshot. Projects without a failing test still need reproduction first.
Monorepos with mixed languages need per-language snapshots. One Python AST walk will not cover Go exports. Extend the snapshot tool before claiming the gate holds.
What the two artifacts buy
The bisect SHA stops a model from rewriting history. The API snapshot stops a model from shipping new surface. Together they keep a bugfix inside one commit's blast radius.
Reviewers then read a small and bounded diff. They compare every changed path to allowed_files.txt. They also require an empty api.snap diff.
That is the merge bar for this class of patch. It is mechanical and cheap to re-run. It leaves taste debates out of the bugfix.
Maintainers with both artifacts can run this review loop. A free server can host that loop if local GPUs are missing. The contract stays in the issue, and the model stays inside it.
Top comments (0)