patchpilot 改為每週一自動升級 Python 依賴並開啟 PR
My AI agent now opens its own pull requests every Monday
作者把 patchpilot 做成 GitHub Action,每逢星期一挑選一個過時套件,在沙盒中升級並修復程式,直至測試通過後開啟 pull request。
A few days ago I wrote about patchpilot, an agent that upgrades a Python dependency and fixes the code the new version breaks (part 1 is here). It worked, but it had one honest flaw: you had to remember to run it. And "I'll do the upgrade later" is exactly how dependencies end up two major versions behind.
So I made patchpilot a GitHub Action. You add one workflow file, and every Monday it picks an outdated package, upgrades it in a sandbox, fixes the code until the tests pass, and opens a pull request.
That's the log from its first real run, replayed.
Here's what happened in that run, with nobody at a keyboard:
- skipped pydantic, which already had an open patchpilot PR
- picked marshmallow 3 → 4
- found that version 4 removed the
missing=anddefault=field options, and the test module crashed on import - renamed them to
load_default=anddump_default=, and all 9 tests passed - opened this pull request as
github-actions[bot]
It also flagged a README line that still said "marshmallow 3", and deliberately left it alone to keep the PR focused. That's the judgement I'd want from a human reviewer.
Here's the whole setup:
name: patchpilot
on:
schedule:
- cron: "0 6 * * 1" # every Monday at 06:00 UTC
workflow_dispatch: # adds a "Run workflow" button
permissions:
contents: write
pull-requests: write
jobs:
upgrade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: ilmeedesilva/patchpilot@v0.2.0
with:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
It's on the GitHub Marketplace. Getting there took more decisions than I expected. These are the ones worth sharing.
1. Don't use AI for the boring decision
The first question is which package to upgrade. I could have asked the model. I didn't, because this decision has a clear right answer, and code gives it for free, instantly, and the same way every time.
patchpilot's rule: a package is a candidate when its newest release on PyPI falls outside the range you declared. pydantic>=1.10,<2 while 2.x is out? Candidate. An unpinned requests? Never, because pip already installs the newest version. Among the candidates it picks the smallest jump first (patch, then minor, then major), because the safest upgrade is the one most likely to get merged.
One rule makes the weekly schedule work: skip any package that already has a patchpilot/upgrade-* branch. Without it, every Monday would redo the same upgrade. There was a catch, though. On a GitHub runner, actions/checkout fetches only the default branch, so a local branch check finds nothing. patchpilot asks the remote directly:
git ls-remote --heads origin 'patchpilot/upgrade-*'
My rule of thumb now: use the model for the parts that need judgement (reading a changelog, fixing code). Use code for everything else.
2. Quiet weeks should cost nothing
A scheduled job that spends money every week to find out there's nothing to do is a job people switch off. So when every outdated package already has an open PR, patchpilot prints "Nothing to upgrade" and exits successfully, without a single API call. The run takes about a second.
Exit code 0 matters here too. If "nothing to do" were an error, your Actions tab would turn red every quiet week, and people learn to ignore red.
3. A throwaway machine is not a sandbox
GitHub gives every job a fresh virtual machine that's deleted afterwards. So it was tempting to drop patchpilot's Docker sandbox in CI and run everything directly on the runner.
But while the job runs, that machine holds your Anthropic API key and a GitHub token that can push to your repo. Meanwhile it's installing a new version of a package and running a test suite: code you didn't write. A compromised release could read those secrets from the environment in one line.
So the sandbox stays. The project's code runs in a container that sees only a temporary copy of the repo, and never the environment the secrets live in. "Throwaway" protects the next job. It doesn't protect this one.
4. Small details that would have bitten users
-
Never paste inputs into shell scripts. A composite action that writes
${{ inputs.package }}straight intorun:lets a crafted input run shell commands. Every input goes through an environment variable instead. -
Pin the tool to the action. The action installs patchpilot from its own source (
pipx install "$GITHUB_ACTION_PATH"), not "whatever's latest on PyPI". So@v0.2.0always means patchpilot 0.2.0. -
Logged in isn't the same as
gh auth status. Locally, patchpilot checksgh auth statusbefore doing any work. In Actions the token comes from an environment variable, and that check can fail even though pushing and opening PRs work fine. Now a token inGH_TOKENcounts as logged in. - Test the action in CI without a key. CI runs the real action on a tiny project where nothing is outdated. That exercises install, picking and the exit code on GitHub's machines, without ever calling Claude.
5. The demo taught me more than the code did
To test it I needed a second outdated package in the demo repo, one that really breaks. My first pick was semver, since version 3 was supposed to remove the old module functions. Before building anything, I installed both versions and checked. Version 3 had kept them, only deprecated. That demo would have been an upgrade where nothing broke, and nothing for patchpilot to fix. I switched to marshmallow 4, which really did remove missing=.
The other trap: adding marshmallow to requirements.txt would have given my live pydantic PR a merge conflict, since that PR edits the same lines. It went into a separate requirements-api.txt instead. Small, but a "live example" with a red conflict banner isn't much of an example.
Even publishing had a lesson. The Marketplace rejected the name "patchpilot" because a GitHub account already has it. The failed release still created the git tag, so it's worth checking for stray tags before you try again.
What's next
The next goal is the one that matters most: real users. I'll run patchpilot on a few open-source projects with dependencies stuck behind an old major version, review every diff by hand, and ask the maintainers before opening anything. A merged PR in someone else's project is a much better test than any demo.
If you'd like to try it, the README has the copy-paste setup. It needs an Anthropic API key, and you can still run it locally with pipx install patchpilot. Feedback is very welcome, especially an upgrade you'd like to see it attempt.
I'm on LinkedIn if you'd like to connect.
來源:DEV Community · dev.to
