Repository navigation
Conversation
…lve constraints in CI
Root cause: dependabot/fetch-metadata reads previous-version from a
requirement range ("10.0.0,>=9.1.0") and reports semver-major even for a
patch (runs 40478242807 sphinx 9.1.0->9.1.1, 40478270225 virtualenv
21.14.6->21.14.13), so no pip range PR ever matched the patch condition.
Ruled out: the ruff exclusion (dependency-names had no ruff) and
permissions (approve/merge steps were skipped, not failed).
Fix: a `level` step derives the bump from the PR title's old and new
bounds (changed upper bound = major, unreadable title = skip); exact pins
and Actions bumps keep fetch-metadata's type. The title reaches the script
only through env.
Merging these unattended needs CI to test them, and it didn't:
setup-python-deps never passed -c constraints.txt (its README said it did),
and pip ignores constraints for packages an install doesn't pull in
(sphinx, tox, virtualenv, pre-commit). The action now installs with
-c constraints.txt, and the required "Code Linting and Formatting" job
resolves the whole file (pip install --dry-run -r constraints.txt, ~5 s),
so a floor that doesn't exist or conflicts fails a required check before
auto-merge can land it.
tests/test_dependabot_auto_merge_workflow.py runs the workflow's and the
action's own scripts (bash 3.2 and 5) against the metadata of the real
runs; 22 of its 23 tests fail against main's files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR fixes Dependabot patch auto-merge for constraints.txt range updates by deriving semver severity from PR-title bounds, safely routing that result through workflow gates, and making CI actually apply and fully resolve constraints so invalid ranges fail required checks before merging. Sequence diagram for constraints range validation and auto-mergesequenceDiagram
participant Dependabot
participant Workflow as GitHub Actions
participant Level as Derive bump level
participant Lint as Required lint check
participant Pip
participant Merge as Auto-merge
Dependabot->>Workflow: Open constraints.txt update PR
Workflow->>Level: Read PR title and fetch-metadata
Level-->>Workflow: update-type
Workflow->>Lint: Run Check constraints.txt resolves
Lint->>Pip: install --dry-run --ignore-installed -r constraints.txt
Pip-->>Lint: Resolution result
alt patch-level lower-bound update and checks pass
Workflow->>Merge: Approve and enable auto-merge
else changed upper bound, invalid title, or failed resolution
Workflow-->>Dependabot: Skip auto-merge
end
Flow diagram for deriving the Dependabot bump levelflowchart TD
A[Read fetch-metadata and PR title] --> B{Previous version is exact}
B -->|Yes| C[Keep fetch-metadata update type]
B -->|No| D[Parse old and new requirement bounds]
D --> E{Title and bounds are readable}
E -->|No| F[Skip auto-merge]
E -->|Yes| G{Upper bound changed}
G -->|Yes| H[Classify as semver-major]
G -->|No| I[Compare lower-bound versions]
I --> J[Classify patch, minor, or major]
C --> K{Update type is patch}
H --> K
J --> K
K -->|Yes| L[Approve and enable auto-merge]
K -->|No| F
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🔒 Security Analysis ReportSecurity Analysis ReportGenerated: Wed Oct 28 00:56:27 UTC 2026 Bandit Security ScanPip-Audit Results |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path=".github/workflows/dependabot-auto-merge.yml" line_range="86" />
<code_context>
+ env:
+ META_TYPE: ${{ steps.meta.outputs.update-type }}
+ PREV: ${{ steps.meta.outputs.previous-version }}
+ TITLE: ${{ github.event.pull_request.title }}
+
- name: Approve patch bump
</code_context>
<issue_to_address>
**Major updates get auto-merged**
When a patch-classified run is delayed while Dependabot updates the PR to a non-patch bump, the run keeps the patch classification from its event while `gh pr merge --auto` targets the PR's current state, so the newer non-patch update gets auto-merge enabled and lands once its checks pass.
Before enabling auto-merge, verify that the PR's current head still matches the head and update classified by the run.
Also at `.github/workflows/dependabot-auto-merge.yml:87`, `.github/workflows/dependabot-auto-merge.yml:97`, `.github/workflows/dependabot-auto-merge.yml:108`.
</issue_to_address>
### Comment 2
<location path=".github/workflows/dependabot-auto-merge.yml" line_range="58-59" />
<code_context>
+ if ! printf '%s' "$PREV" | grep -Eq '^[0-9]+(\.[0-9A-Za-z]+)*$'; then
+ # Range update: compare the bounds in "update X requirement from <old> to <new>"
+ level=""
+ old=$(printf '%s' "$TITLE" | sed -nE 's/.* requirement from ([^ ]+) to ([^ ]+)( in .*)?$/\1/p')
+ new=$(printf '%s' "$TITLE" | sed -nE 's/.* requirement from ([^ ]+) to ([^ ]+)( in .*)?$/\2/p')
+ old_lo=$(printf '%s\n' "$old" | tr ',' '\n' | sed -n 's/^>=//p')
+ new_lo=$(printf '%s\n' "$new" | tr ',' '\n' | sed -n 's/^>=//p')
</code_context>
<issue_to_address>
**Edited titles can trigger major auto-merges**
When a Dependabot PR title is changed to describe a patch while its constraints change is major, `level` classifies the bounds in `TITLE` without checking them against the changed constraints file, so a Dependabot PR retitled with patch-level bounds is approved and auto-merged even when its actual change is major; callers see a major dependency update merge without the intended review.
Derive the level from the actual old and new requirement bounds in the PR diff, or verify the title’s bounds against those values before allowing auto-merge.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 2 findings to address first, and the derived level controls whether a constraints-only dependency update is approved and auto-merged; if the title parsing classifies a non-patch change as a patch, the new dependency version can be installed and its code can run in CI without review. Reverting restores the constraints, but cannot undo any secrets or data exposed by code that already ran.
Blocking findings: .github/workflows/dependabot-auto-merge.yml:86, .github/workflows/dependabot-auto-merge.yml:59
| env: | ||
| META_TYPE: ${{ steps.meta.outputs.update-type }} | ||
| PREV: ${{ steps.meta.outputs.previous-version }} | ||
| TITLE: ${{ github.event.pull_request.title }} |
There was a problem hiding this comment.
🟡 Medium · Major updates get auto-merged
When a patch-classified run is delayed while Dependabot updates the PR to a non-patch bump, the run keeps the patch classification from its event while gh pr merge --auto targets the PR's current state, so the newer non-patch update gets auto-merge enabled and lands once its checks pass.
Before enabling auto-merge, verify that the PR's current head still matches the head and update classified by the run.
Also at .github/workflows/dependabot-auto-merge.yml:87, .github/workflows/dependabot-auto-merge.yml:97, .github/workflows/dependabot-auto-merge.yml:108.
Prompt for AI agents
In `.github/workflows/dependabot-auto-merge.yml` at line 86:
**Major updates get auto-merged**
When a patch-classified run is delayed while Dependabot updates the PR to a non-patch bump, the run keeps the patch classification from its event while `gh pr merge --auto` targets the PR's current state, so the newer non-patch update gets auto-merge enabled and lands once its checks pass.
Before enabling auto-merge, verify that the PR's current head still matches the head and update classified by the run.
Also at `.github/workflows/dependabot-auto-merge.yml:87`, `.github/workflows/dependabot-auto-merge.yml:97`, `.github/workflows/dependabot-auto-merge.yml:108`.| old=$(printf '%s' "$TITLE" | sed -nE 's/.* requirement from ([^ ]+) to ([^ ]+)( in .*)?$/\1/p') | ||
| new=$(printf '%s' "$TITLE" | sed -nE 's/.* requirement from ([^ ]+) to ([^ ]+)( in .*)?$/\2/p') |
There was a problem hiding this comment.
🟠 High · Edited titles can trigger major auto-merges
When a Dependabot PR title is changed to describe a patch while its constraints change is major, level classifies the bounds in TITLE without checking them against the changed constraints file, so a Dependabot PR retitled with patch-level bounds is approved and auto-merged even when its actual change is major; callers see a major dependency update merge without the intended review.
Derive the level from the actual old and new requirement bounds in the PR diff, or verify the title’s bounds against those values before allowing auto-merge.
Prompt for AI agents
In `.github/workflows/dependabot-auto-merge.yml` at lines 58-59:
**Edited titles can trigger major auto-merges**
When a Dependabot PR title is changed to describe a patch while its constraints change is major, `level` classifies the bounds in `TITLE` without checking them against the changed constraints file, so a Dependabot PR retitled with patch-level bounds is approved and auto-merged even when its actual change is major; callers see a major dependency update merge without the intended review.
Derive the level from the actual old and new requirement bounds in the PR diff, or verify the title’s bounds against those values before allowing auto-merge.
Summary
The native patch auto-merge (#344) never fired on pip range updates in
constraints.txt.dependabot/fetch-metadatareadsprevious-versionfrom the range string (10.0.0,>=9.1.0) and reportsversion-update:semver-majoreven for a true patch, so every such run showed green while skipping the approve and merge steps (sphinx 9.1.0 → 9.1.1 in run 40478242807, virtualenv 21.14.6 → 21.14.13 in run 40478270225). Ruled out: the ruff exclusion (dependency-nameshad no ruff) and permissions (the steps were skipped, not failed).Classifying them correctly would not be enough on its own, because CI never resolved these ranges.
setup-python-depsnever passed-c constraints.txt(its README claimed it did), and pip ignores a constraint for any package an install doesn't pull in. sphinx, tox, virtualenv and pre-commit aren't installed in CI at all.Changes
dependabot-auto-merge.yml: a newlevelstep derives the bump from the PR title's old and new bounds: a changed upper bound counts as major, and a title it can't read counts as skip. Exact pins and Actions bumps keep fetch-metadata's type. The approve, merge and skip steps gate onsteps.level.outputs.update-type. The title reaches the script only throughenv:, and the output is one of three fixed strings.setup-python-deps/action.yml: the requirements and package installs use-c constraints.txtwhen the file exists.lint.yml(the required "Code Linting and Formatting" check): a new step resolves the whole file withpip install --dry-run --ignore-installed -r constraints.txt(about 5 s). A floor that doesn't exist or conflicts now fails a required check before auto-merge can land it. The step runs onpull_requestwith a read-only token.tests/test_dependabot_auto_merge_workflow.py: 23 tests run the workflow's and the action's own scripts against the metadata of the real runs, under bash 3.2 and bash 5.Verification
main's three files. The remaining one, the no-constraints-file path, passes on both by design. All 23 pass with the fix, under/bin/bash3.2 and bash 5.3.uv pip compile, linux, Python 3.12 and 3.13): requirements plus.[dev,test]resolve under-c constraints.txt, and the whole ofconstraints.txtresolves as requirements. Both hold formain, for each of the open PRs deps(deps): update pre-commit requirement from >=4.6.2 to >=4.7.0 #342 and deps(deps): update sphinx requirement from <10.0.0,>=9.1.0 to >=9.1.1,<10.0.0 #346 to deps(deps): update coverage requirement from <8.0.0,>=7.16.2 to >=7.17.0,<8.0.0 #350, and for all six combined. A probe withsphinx>=9.99.0fails, as intended.pip checkis clean. Thelint.ymldry-run passed onmain(5 s) and on all PRs combined, and returned exit code 1 on the bad floor.After merge, the open range PRs need
@dependabot rebasebefore the workflow re-evaluates them. Only #346 (sphinx) and #348 (virtualenv) are patch-level; the rest stay manual.🤖 Generated with Claude Code
Summary by Sourcery
Make Dependabot safely auto-merge patch-level constraints updates while ensuring all constraint ranges are resolved by required CI checks.
New Features:
Bug Fixes:
Enhancements:
CI:
Documentation:
Tests: