How to Create Merge-Ready AI Code Fixes That Engineers Trust

A finding lands: path traversal (CWE-22) in a file-export handler. The name parameter on GET /exports/download flows straight into a filesystem join. Triage marks it confirmed-prod. The exploit agent sends name=../../../etc/passwd and gets a 200 with the file in the body. Reproduced.

The patch is the easy part. It is not what this post is about.

Watch the AI autofix PR instead. It opens as a draft, with the export service's code owners already requested as reviewers. The description leads with the request and response that proved the bug. CI runs and one test fails. The agent reads the log, sees the failure is its own, and pushes a corrective commit. CI goes green and the PR flips to ready. A reviewer comments: we have a safeJoin helper for this, use it. The agent swaps the call, replies on the thread and pushes. The reviewer approves.

Nobody on the security team touched it. The endpoint is invented. The behavior is not.

A patch is an answer. A pull request is a question you ask a person.

What makes an autofix PR merge-ready

A merge-ready autofix PR is an AI-generated security fix that a developer can merge with no edits and no back-and-forth. It answers five reviewer questions before anyone asks them: should this PR exist, is the change minimal, why should I believe it, why am I reviewing it, and is it finished.

A correct diff is not a merged fix. Between the two sits a developer with a sprint, a review queue and years of practice at tuning out security tooling. They approve, push back or leave it for later. Later is where security fixes go to die. Miss one of the five questions and you get a comment thread, a stalled branch or a silent close.

The five questions a merge-ready AI autofix PR answers before review: should it exist, is it minimal, why believe it, why this reviewer, is it finished

Should this PR exist?

The cheapest PR to review is the one that was never opened. In one enterprise proof-of-value, a library with four critical and ten high CVEs triaged out as negligible because it never ran in production. A pipeline that skips that check still writes four PRs for it.

So the decision belongs upstream, in triage. Only reachable findings worth fixing become candidates. Dead code, test-only paths and findings that need architectural rework are skipped.

Declining is explicit: a typed verdict with a reason, not a PR that quietly half-works. Caps keep each batch small, because ten correct PRs in one morning still read as noise.

A skipped finding costs nothing. A wrong PR costs trust.

Is the change as small as it can be?

Reviewers merge small diffs and argue with large ones. The fix should be the smallest change that closes the root cause, planned before any file is edited.

Small is not naive. Validation belongs at the real trust boundary, not three layers downstream where the model found the sink. An unreachable transitive dependency is left alone rather than forced through a breaking upgrade.

Minimal means minimal for this codebase, not minimal in general.

Why should I believe it?

A generic "this PR addresses a potential security issue" hands triage back to the developer. A merge-ready description carries the evidence triage already produced:

  • What was found: the weakness class and the exact file:line.
  • Why it matters here: the reachability verdict and the path from the internet.
  • Proof: the request and response that triggered it, and a way to reproduce it.
  • Severity and exploitability: stated as verdicts, not adjectives.
A generic autofix PR description beside a merge-ready one that lists file and line, reachability, proof and severity verdicts

Tone follows the evidence. Proven and reachable reads direct and urgent. Uncertain reachability says so plainly. It never claims more confidence than the evidence supports.

Confidence is a claim too. Anchor it or drop it.

Why am I the one reviewing it?

A PR assigned to the wrong person waits. The right reviewer comes from three sources that often disagree: the code owners, the team that owns the files, and the people who last worked on them.

Capacity matters as much as ownership: someone sitting on five open review requests is not really available. Rerouting has a cost, since CODEOWNERS is a control, so settle that first if it matters for audit.

The right reviewer is the one who can act, not just the one who owns the file.

Is it finished?

A reviewer should never be the first to learn CI is red. The PR stays in draft until your own CI is green, because your CI already defines what "doesn't break anything" means.

When a check fails, the agent reads the failing log and first checks whether the same failure exists on the base branch. It does not chase breakage it did not cause. If the failure is its own, it pushes a fix within a commit budget. Review comments get the same treatment: make the change, reply in the plain tone of a colleague.

Draft means the machine is still working. Ready means it's your turn.

Lifecycle of an AI autofix PR from draft through CI and review to human approval or handoff

When the agent stops

When the iteration budget runs out, the PR stays in draft and the agent hands off to a person. Nothing merges without human approval, and PR-gate controls ship off by default.

Some limits belong in the open: exploit replay against patched code runs in our evaluation harness today, not yet as a blocking gate on customer PRs. When the proof a finding admits is a settled CI run or a re-resolved lockfile, that is the proof we use, and we say so.

What this looks like in Nullify

Nullify runs the whole loop: detection, triage, exploit validation, fix and the chase to merge. Autofix PRs integrate natively with GitHub, GitLab and Buildkite.

The number we hold ourselves to is the merge-ready rate: the share of AI autofix PRs merged with no further edits and no back-and-forth. Opened is activity. Merged untouched is trust.

A patch is an answer. A pull request is a question you ask a person. A merge-ready pull request is a question with one obvious answer.

See how Nullify takes a finding from triage to a merge-ready fix.

Nullify