@@ diff --reward unit_test → unit_test + F1 @@
PreciseCoder:
Faithful and Generalizable Debugging
Beyond Unit-Test Rewards
Reinforcement learning from unit tests only asks whether the final program passes. It raises pass rates — and quietly teaches models to edit more and to hallucinate bugs in code that is already correct. PreciseCoder adds a reward for semantically verified, localized edits.
13.8→3.0edited lines per task on Qwen3.5-4B, at the same pass rate as unit-test-only GRPO- +24–29points of edit precision over the base models (4B, 9B, 27B)
- 47–52%no-op rate on correct code, vs. 19–31% after unit-test-only GRPO
- +4.6–12.4precision points for frontier models such as GPT-5.6 Sol and Qwen3.8-Max, given a 9B PreciseCoder reasoning trace
Two failure modes a unit test can't see
Toggle between the two cases. A unit-test reward scores the program the model returns — not the edit that produced it.
Illustrative examples adapted from Figure 1(a) of the paper.
Diagnosis: the unit-test reward teaches over-editing
We train Qwen3.5-4B with GRPO on PDB debugging tasks using only the unit-test reward and watch the rollouts. Early on, edit size tracks pass rate. Once the pass rate plateaus, the reward keeps encouraging larger edits: the mean edit on passing rollouts grows from 3.94 to 8.28 lines.
Training never shows the model a correct program — yet on 200 held-out bug-free programs the no-op rate falls from 30.8% to 20.5%. The model learns to “fix” code that isn't broken.
Even frontier models over-edit: on PDB, GPT-5.1-Codex passes 76% of tasks, yet only 40% of the lines it edits are necessary.
Method: reward the edit, not just the program
Edit size alone barely tracks correctness, so penalizing it trades pass rate for smaller patches. PreciseCoder instead scores each edit fault by fault.
-
1
Verify each edit semantically
Each predicted edit block \hat\delta_i is aligned to a reference fix \delta^\star_i. Fault i counts as fixed iff swapping the model's block into the reference repair still passes the tests.
\mathrm{rec}=\frac{1}{m}\sum_i \mathrm{fix}_i\mathrm{prec}=\frac{1}{\ell}\sum_i \mathrm{fix}_i\,\min\!\big(|\mathrm{ess}_i|,\,|\delta^\star_i|+\epsilon\big)Recall asks which faults were repaired; precision asks what fraction of edited lines were needed.
-
2
Gate recall, combine into F1
A passing program has resolved every injected fault by definition, so recall is set to 1 when tests pass. F1 of precision and gated recall correlates with both passing and, among passing programs, with smaller edits.
\widetilde{\mathrm{rec}}=\begin{cases}1 & \mathrm{unit}=1\\ \mathrm{rec} & \text{otherwise}\end{cases}F_1=\mathrm{HM}\big(\mathrm{prec},\,\widetilde{\mathrm{rec}}\big) -
3
Normalize each term separately
Train with GDPO, which standardizes each reward term within the group before mixing, so a passing repair keeps a larger advantage than a failing one whenever \lambda < 0.37 at G=6.
r_\lambda=(1-\lambda)\,\mathrm{unit}+\lambda\,F_1\lambda=\tfrac{1}{3}
Why F1? Two tests for a reward term
Each candidate term is scored by its correlation with passing (stability, x-axis) and, among passing samples, with smaller edits (fidelity, y-axis).
Edit size (−ℓ) fails the first; the unit test, constant once passing, fails the second. Precision tracks smaller edits, recall tracks passing, and only F1 lands in the desired corner.
Pearson r averaged over all training tasks (circle 4B, square 9B; 16 samples per task). The unit test is plotted at y = 0.
Results: precision without giving up pass rate
Trained on 5,274 single-line-bug tasks from PDB-Single-Full (BigCodeBench + LiveCodeBench), tested on 1,205 held-out tasks from PDB-Single, its verified subset, and on 1,512 already-correct programs (GT Debug). Both splits come from PDB, the Precise Debugging Benchmark.
| PDB Test (buggy code) | GT Debug (correct code) | |||||
|---|---|---|---|---|---|---|
| Method | Pass % ↑ | Precision % ↑ | Recall % ↑ | Lines ↓ | No-op % ↑ | Damage % ↓ |
PreciseCoder rows are mean ± std over 3 runs. Within each block: best second best
Edit-size penalties cost pass rate
RECAP and EA-GRPO cut edits on Qwen3.5-4B from 13.8 to ≤3.7 lines, but pass rate drops from 55.1% to 38.1% and 53.3%. PreciseCoder reaches 3.0 lines at 55.3%.
27B is competitive with frontier models
PreciseCoder-27B beats its GRPO counterpart, DeepSeek-V4.1-Flash and Qwen3.8-Flash-Next on every measure. GPT-5.6 Sol passes more tasks, but its precision is 17.7 points lower.
Restraint comes for free
No training row contains correct code, yet the no-op rate on correct programs rises to 46–52% across 4B, 9B and 27B, and PreciseCoder breaks fewer of them than the base model.
Trained on single-line bugs. Generalizes beyond them.
Training only ever shows single-line faults in single-file programs, repaired in one shot.
Weak-to-strong: precision travels through the reasoning trace
We prefill the think block of a 9B PreciseCoder model — token for token — into an untrained receiver, which writes the repair. No weights, tokenizer or training are shared. Five stronger receivers each gain 4.6–12.4 points of edit precision while their pass rate moves by −1.5 to +4.3.
Prefilled into its own base model, the trace recovers 73% of the training pass-rate gain and 60% of the precision gain. As a control, an untrained 9B trace costs Qwen3.6-27B 7.4 points of pass rate.
What changed in the thinking
Either reward makes the model think longer. Only PreciseCoder changes what it says about a line.
− unit-test GRPO
- “let's check”
- “only fix”
- “make minimal”
Restates the instruction to fix. Argues from the prompt that a bug must exist (0.59 / 1k words).
+ PreciseCoder
- “might be”
- “is valid”
- “keep the”
Weighs whether a line is worth keeping. Hedges more (10.8 vs 6.5 / 1k) and almost never assumes a bug must exist (0.04 / 1k).
The reward credits an action only when it is checked against a reference and verified to do the work the reference requires — so a correct but unnecessary action earns nothing.