When a CodePeel review produces findings that carry concrete code suggestions, the auto-fix pipeline can collect them, apply them, and open a real pull request on your feature branch. It runs in the background after every PR review, in parallel with comment posting, and it either produces a reviewable fix PR or quietly does nothing. This article walks the pipeline end to end: how findings are selected, why fixes are applied as whole-file rewrites, how the commit is assembled through GitHub's Git Trees API, and what the generated pull request looks like.
Auto-fix is available on Pro and Max plans and is disabled by default — nothing is generated until you opt in.
Turning it on
You can enable Auto-Fix directly in the CodePeel Web Dashboard:
- Per repository: Navigate to Repositories → select your repo → click Repository Config and toggle Auto-Fix on (or in the YAML tab:
auto_fix.enabled: true). - Globally: Navigate to Settings → Automation and toggle Auto-Fix Findings on.
Repository-level settings take precedence over global settings.
That is the entire configuration. There is no per-finding selection and no allow-list of files — the pipeline applies every finding that passes the fixability filter described next.
Step 1: deciding what is fixable
After the review completes, the pipeline filters its findings down to the auto-fixable subset. The filter is mechanical — a finding must have all three of:
- a concrete code suggestion (
problemCode/fixCodeor its equivalent), - a valid line number, and
- a valid file path.
A finding without a suggested fix — architectural feedback, "this function is too long", a missing test that needs domain judgment — is not fixable by definition and is skipped. The survivors are grouped by file path, so each affected file is processed exactly once with all of its fixes applied together.
In practice, the fixable set skews toward localized changes: parameterizing a query, replacing eval(), adding a null check, memoizing a value, fixing an import, applying const over let. Anything requiring cross-file coordination or design judgment stays out, because the rewrite step only sees one file at a time.
Step 2: whole-file rewrites, not line splicing
This is the most important design decision in the pipeline. Each finding's suggestion is a snippet, not a unified diff, and naively pasting snippets into a file by line number breaks as soon as two fixes overlap or one fix changes the length of the region below it. So instead of splicing lines, the pipeline:
- Fetches the file's current content from the repository at the pull request's head commit (the GitHub contents API returns it base64-encoded; it is decoded to UTF-8).
- Sends the full file, together with all the findings and suggested fixes for that file, to the fix generator.
- Receives back one rewritten version of the file with every fix applied.
The generator sees the whole file — imports, indentation, surrounding code — so fixes land coherently. The rewritten content is compared with the original, and files with no actual change are dropped from the set. If every file ends up unchanged, the pipeline stops here and no pull request is created.
The trade-off is worth stating plainly: because the model rewrites the file rather than applying surgical patches, it can occasionally alter code unrelated to the listed findings. That risk is the reason the generated PR exists as a reviewable pull request and not as a direct push to your branch.
Step 3: assembling the commit through the Git Trees API
The modified files are committed with GitHub's lower-level Git data APIs, which avoid checking anything out or pushing through a working copy:
1. GET the original PR → head ref name, head SHA
2. GET the head commit → its tree SHA
3. POST /git/trees (base_tree = head tree, blobs = rewritten files)
4. POST /git/commits (tree = new tree, parents = [head SHA])
5. POST /git/refs (refs/heads/codepeel/fix-pr-<N>-<timestamp> → new commit)
6. POST /pulls (head = new ref, base = your feature branch)
Because the new tree is built on top of the head commit's tree (base_tree), only the changed blobs are uploaded, and the new commit's parent is the PR's head commit — the fix branch is a true child of your work, not a divergent copy.
The commit message follows a fixed convention:
fix(codepeel): apply 3 automated fixes
Co-authored-by: CodePeel <bot@codepeel.com>
and the branch is named codepeel/fix-pr-<pullNumber>-<timestamp>, for example codepeel/fix-pr-42-1717200000000.
The generated pull request
The fix PR targets your feature branch, not main. That is deliberate: the fixes merge alongside your original code and ship together with it, and your open PR's checks and review flow stay in charge of what lands. The PR is titled 🤖 [CodePeel] Auto-Fixes for PR #42 and its body includes the count of modified files and an explicit review checklist — verify indentation and structure, confirm referenced types exist, run your test suite. A short comment with a link to the fix PR is posted on the original pull request so the reviewers know it exists.
A typical applied fix looks exactly like what a careful human would have written:
--- a/src/db/users.ts
+++ b/src/db/users.ts
@@ -39,7 +39,7 @@
async findByEmail(email: string) {
- const result = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
+ const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
return result.rows[0];
}
Every fix PR passes through your normal review and CI — nothing about it is privileged, and merging it is a human decision.
Quota and failure behavior
Generating a fix PR consumes one review from your allowance, deducted only when a fix PR is actually created. If the review produced no fixable findings, if generation fails, or if the files cannot be fetched, nothing is consumed and nothing is posted.
The pipeline runs as a background operation alongside inline-comment posting, so a slow or failed fix generation never delays or degrades the main review — your PR still receives its findings and comments whether or not the fix PR materializes.
What does not get auto-fixed
Some categories are structurally out of scope:
- Findings without code suggestions — the filter in step 1 excludes them by definition.
- Architectural and multi-file changes — each file is rewritten independently; there is no cross-file coordination, so a fix that depends on a change in another file will not be attempted.
- Deleted or renamed files — a fix targeting a path that no longer exists at the head commit fails silently for that file.
- Compiled or tested correctness — generated fixes are not compiled or run before being committed. The fix PR may contain code that does not build; that is what your CI and the review checklist are for.
Reviewing a fix PR well
Treat the fix PR as a starting point written by a fast but non-contextual assistant:
- Read the full diff, not just the hunks near findings — the whole-file rewrite is the mechanism that makes fixes coherent, and the mechanism that can introduce drift.
- Check the diff against each original finding's inline comment to confirm the applied fix matches the suggestion.
- Let CI run before merging; the pipeline deliberately does not validate compilation or tests.
- If new commits have landed on the feature branch since the review ran, lines may have shifted — push another commit to trigger a fresh review with fresh findings rather than hand-merging stale fixes.
Where auto-fix fits in the workflow
Auto-fix closes the loop that starts with the security and quality analysis: findings become comments, comments become a branch, and the branch becomes a mergeable PR you review on your own schedule. For interactive fixing instead of batch fixing — apply one suggestion locally, or route one finding to an AI agent in your editor — see the VS Code inline review walkthrough, and the auto-fix docs for the operational reference.