Claude Code Routines: Auto-Maintaining PR Evolution Numbers

5 min read
Listen to this post

I had a backend PR open all week, waiting for a scheduled maintenance window before it could merge. The PR adds a single database evolution, a SQL migration that creates an index needed before the deploy.

Play Framework evolutions are numbered sequentially: 1, 2, 3, each a SQL file. When you add a new evolution, you take the next number. The problem is that every other PR merging to master that week was also adding evolutions. Each time one landed, it took the number mine was using.

Rebase. Renumber. Push. Wait. Another PR merges. Rebase again.

There had to be a better way.

The ask

The fix wasn’t complicated. Check if the evolution number on my branch conflicts with what’s on master. If it does, renumber to the next available slot, rerun code generation, and force-push. The steps were mechanical. Doing them four times in a week was the problem. This felt like a great fit for Claude Code’s recently released feature: routines.

So I described it to Claude in one sentence.

Can you monitor this daily for breaking changes to the PR and keep it up to date with master? Resolving any issues with the evolution if the number gets taken again and updating the PR accordingly.

That’s the entire prompt. No pseudo-code. No step-by-step instructions. Just the constraint and the desired outcome.

What Claude generated

Claude wrote a routine to handle the full workflow. A routine is a saved prompt that runs on a schedule against a repository. Routines can run on Anthropic’s cloud infrastructure or locally on your machine. I used a local routine for this one.

Here’s the generated routine, sanitized and condensed. This is what Claude produced from the one-sentence ask above.

Monitor PR #1234 on org/repo and keep the branch in a
mergeable state with master. The PR adds one database
evolution. Evolution-number collisions with other PRs
merging to master are the recurring issue.

## Workflow

1. Check PR state via `gh pr view` — if not OPEN, stop.

2. Find our evolution file by grepping for the index
   identifier in the evolutions directory.

3. Check for collision — does master already have a file
   at our evolution number?

4. If no collision and branch is current with master,
   stop silently. No "all clear" comments.

5. If collision: capture our SQL content, find the highest
   evolution number on master, renumber to master_max + 1,
   reset to master, write the file at the new number.

6. Rerun sbt evolutions and code generation. If sbt fails
   (local Postgres not reachable), proceed but flag it.

7. Commit, force-push with lease, update the PR body to
   reflect the new evolution number.

8. Post a PR comment summarizing what changed: which
   master PR landed at the old number, the rename from
   N.sql to M.sql, and anything needing manual follow-up.

## Constraints
- Don't touch other PRs. Don't merge this one.
- Force-push with lease is fine on this feature branch.
- Only comment when you take action or something needs
  manual attention.
- Idempotent: running twice with no upstream changes
  should do nothing the second time.

In short: check the PR state, find the evolution file, detect whether master already has a file at the same number, and if so, renumber to the next available slot, rerun code generation, force-push, and comment on the PR with a summary. If nothing changed, do nothing and stay quiet.

Two things stand out.

First, the constraints section. I didn’t specify most of those. Claude inferred the boundaries from the problem description: don’t merge, don’t touch other PRs, be idempotent, stay quiet when there’s nothing to do. Those are the constraints an experienced developer would set. The routine arrived with them already in place.

Second, the idempotency requirement. A routine that runs daily needs to be safe to run twice. Claude treated that as a given rather than something I needed to spell out.

The routine handled four more renumbers that week without me touching it. Each morning it checked, and on the days a collision had occurred overnight, it renumbered, pushed, and left a comment on the PR explaining what changed.

The mental model

The pattern here isn’t specific to Play evolutions or database migrations. It applies to any mechanical maintenance task on a long-lived PR. Describe the constraint, not the steps. I didn’t write the bash commands or the git operations. I described the problem (evolution numbers collide when other PRs merge) and the desired outcome (keep my PR mergeable). Claude filled in the implementation because the steps follow directly from the constraint.

This is the same dynamic that makes Claude Code effective for coding tasks: the more precisely you describe what you need and why, the better the output. The difference with routines is that the automation keeps running until the PR merges.

If you’re babysitting a PR this week, describe the problem. Let the agent write the automation.


If you’re building AI into your engineering workflows and want to move faster without the trial-and-error, let’s talk about how I can help.

Further reading

More on building real systems

I write about AI integration, architecture decisions, and what actually works in production.

Occasional emails, no fluff.

Powered by Buttondown