One fix, several release branches: applying a change to every version you maintain

25 October 2026

Plenty of repositories do not have one main line. A library keeps a 2.x and a 3.x branch alive because customers have not all moved. A product ships from release/1.4 while release/1.5 is in testing. A plugin exists once per version of the platform it plugs into. When a bug is found in code that all of those share, the fix is one decision and several pieces of work.

Most AI coding tools, ours included until recently, assume a task means one change against the default branch. Everything after that, the backports and the cherry-picks that do not apply cleanly, was left to a person. This post is about what we built so that a task can name the branches it applies to, and what we deliberately did not build.

A task names its branches

A task can carry a list of branch targets. Each target is one repository at one branch, and each one gets its own checkout and its own pull request into that branch. One task, one approved spec, one run, and as many pull requests as there are branches that needed a change.

You do not have to pick the branches every time. A repository can list its maintained branches, the lines it keeps side by side, and say whether new tasks apply to all of them or only to the default branch. That default is filled in when the task is created, whether from the form, the new-task chat or the API, so you see it on the task and can narrow it there before anything runs. "All versions" in the chat means exactly that list.

Two small rules sit underneath. The same branch of the same repository can only be listed once on a task, because two pull requests from one task into one branch would only conflict with each other. And branch names are checked against a conservative subset of git's rules before they are saved, since they end up in git commands and provider API calls.

One checkout per branch, side by side

When the run starts, each target is cloned into its own directory in the same workspace, named after the repository and the branch. Each gets its own work branch too, with the base branch as a suffix. That last detail is there because of an obvious bug in waiting: with one shared work branch name across several targets of the same repository, every push would overwrite the one before it.

The agent then sees all of them at once. Its instructions say that these may be the same repository at different points in its history, that dependencies, framework versions, APIs and file layout may differ between them, and that it should make the change in every listed directory, adapted to what that branch actually contains rather than copied across. It is also told not to edit one branch's directory to fix another.

That is the main reason we chose one run across all branches over one run per branch. A single agent session that can read the 2.x and the 3.x code together can see why the fix looks different on each. Separate runs would each rediscover the problem on their own.

Afterwards every target goes through the same verify, self-review and security steps as any run. A target where nothing changed is skipped and gets no pull request, which is the honest result when a bug simply does not exist on an older line.

Starting a new version line

The other request we kept hearing was the reverse: not "fix this everywhere" but "start the next version". A target can therefore say that its branch does not exist yet and should be created from another one.

We do that in two steps. First the new branch is pushed as an unchanged copy of the source branch. Then the run does the upgrade work on top of it, and that work arrives as a pull request into the new branch. The alternative, pushing a new branch whose first commit already contains the AI's upgrade, would have made the most important change of the whole line the one nobody reviewed.

The push is never forced. If the branch appeared on the remote in the meantime, it fails instead of overwriting it; if an earlier attempt of the same task already created it, the run builds on that. If the branch to copy from does not exist, the run stops and says which one.

What "upgrading to the next version" means is not something we encode. There is no ecosystem-specific logic in this feature. The agent is told to look at how the earlier lines in the repository differ from each other and learn from that what an upgrade involves here: version metadata, dependencies, migration steps, whatever the project's own history shows. Once the upgrade pull request is open, the repository settings offer the new branch for maintenance; whether it becomes a line you keep is your call, so we do not add it by ourselves.

The thing we got wrong

The chat starts a new line from the newest maintained branch, and the branch picker lists branches in order. Both used plain string sorting at first, which puts 10.0 before 9.0. For any product past version 9, "the newest line" was the wrong one. Branch names now sort naturally, so 9.0 comes before 10.0 and release/2 before release/10. It is a small bug, but it would have produced a new version copied from an old one.

What it does not do

  • It does not forward-port or cherry-pick. Each branch gets its own implementation, which is the point, but it also means each pull request needs its own review.
  • It does not check whether a branch exists when you save the task. That needs the git host, so a missing branch is reported by name when the run starts.
  • More branches means a larger run. The agent reads and changes every listed checkout in one session, so a task with many branches is a bigger run to pay for and more pull requests to review.
  • On self-hosted runners, runs with several targets need a runner recent enough to handle them. Older runners skip such runs, and starting one with no capable runner available is refused with a reason rather than left queued.

Where to start

If you maintain more than one line, list them on the repository first and leave new tasks on the default branch. Then pick one real bug that exists on two of them, set the targets on that task, and compare the two pull requests. The differences between them tell you quickly whether the agent understood your branches or just copied a diff. The spec that both pull requests are built from is described in spec-driven development.

Back to all articles

Test it on a task from your own backlog

Send us one real task. We run CodeLynq on your repository and you review the pull request. Free, within 5 working days.