Skip to content

Add workflow to open esphome bump PR on release - #1862

Merged
bdraco merged 3 commits into
mainfrom
add-esphome-bump-workflow
Aug 11, 2026
Merged

Add workflow to open esphome bump PR on release#1862
bdraco merged 3 commits into
mainfrom
add-esphome-bump-workflow

Conversation

@bdraco

@bdraco bdraco commented Aug 11, 2026

Copy link
Copy Markdown
Member

What does this implement/fix?

Adds a workflow that runs when a release is published; it waits for PyPI to index the new version, then updates the aioesphomeapi pin in esphome/esphome requirements.txt and opens a draft PR there, so the bump lands right away instead of waiting for the next dependabot run. It authenticates with the ESPHome GitHub App, fills in the esphome PR template at runtime, and reuses a single branch so back to back releases update the existing PR instead of opening duplicates. Also supports workflow_dispatch with a version input for manual runs.

Uses the org level ESPHOME_GITHUB_APP_CLIENT_ID variable and ESPHOME_GITHUB_APP_PRIVATE_KEY secret, already available to all org repos, same setup as device-builder-frontend.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

  • fixes

Pull request in esphome (if applicable):

  • esphome/esphome#

Checklist:

  • The code change is tested and works locally.
  • If api.proto was modified, a linked pull request has been made to esphome with the same changes.
  • Tests have been added to verify that the new code works (under tests/ folder).

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (99a5533) to head (b3c04c3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1862   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           26        26           
  Lines         4288      4288           
=========================================
  Hits          4288      4288           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq

codspeed-hq Bot commented Aug 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 56 untouched benchmarks


Comparing add-esphome-bump-workflow (b3c04c3) with main (99a5533)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (045117c) during the generation of this report, so 99a5533 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@bdraco
bdraco marked this pull request as ready for review August 11, 2026 16:22
Copilot AI lite review requested due to automatic review settings August 11, 2026 16:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a GitHub Actions workflow to automatically bump aioesphomeapi in esphome/esphome immediately after an aioesphomeapi release is published (or via manual dispatch), by waiting for the new version to appear on PyPI, updating requirements.txt in the esphome repo, and opening/updating a draft PR on a reused branch.

Changes:

  • Introduces a release(published) + workflow_dispatch workflow that derives the target version and waits for PyPI indexing.
  • Authenticates to esphome/esphome via a GitHub App token and updates the aioesphomeapi pin in requirements.txt.
  • Creates or updates a single draft PR in esphome/esphome using the repo PR template and a stable head branch.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +94 to +105
old = os.environ["OLD"]
new = os.environ["VERSION"]
template = Path("esphome/.github/PULL_REQUEST_TEMPLATE.md").read_text()
description = (
f"Bump aioesphomeapi from {old} to {new}, release notes: "
f"https://github.com/esphome/aioesphomeapi/releases/tag/v{new}"
)
body = template.replace(
"<!-- Quick description and explanation of changes -->", description, 1
)
body = body.replace("- [ ] Other", "- [x] Other", 1)
Path("pr_body.md").write_text(body)
Comment on lines +131 to +134
title="Bump aioesphomeapi from $OLD to $VERSION"
existing=$(gh pr list --repo esphome/esphome --head bump-aioesphomeapi --state open --json number --jq '.[].number')
if [[ -n "$existing" ]]; then
gh pr edit "$existing" --repo esphome/esphome --title "$title" --body-file pr_body.md
Comment on lines +120 to +122
git checkout -b bump-aioesphomeapi
git commit -am "Bump aioesphomeapi from $OLD to $VERSION"
git push --force origin bump-aioesphomeapi
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@bdraco, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa09b7d7-e1cd-4eac-b373-7fbc4380c7d2

📥 Commits

Reviewing files that changed from the base of the PR and between b26f4bf and b3c04c3.

📒 Files selected for processing (1)
  • .github/workflows/bump-esphome.yml

Walkthrough

The PR adds a GitHub Actions workflow that resolves an aioesphomeapi version, waits for PyPI indexing, updates the esphome dependency pin, and creates or updates a draft pull request.

Changes

ESPHome dependency bump

Layer / File(s) Summary
Release resolution and PyPI validation
.github/workflows/bump-esphome.yml
The workflow runs on published releases or manual dispatch. It resolves the target version and verifies that PyPI indexes it.
Pin update and pull-request body
.github/workflows/bump-esphome.yml
The workflow creates a GitHub App token, checks out esphome/dev, validates the existing pin, updates requirements.txt, and generates the pull-request body.
Branch and draft pull-request delivery
.github/workflows/bump-esphome.yml
When the pin changes, the workflow commits and pushes bump-aioesphomeapi, then updates an existing draft pull request or creates one targeting dev.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant PyPI
  participant GitHubApp
  participant ESPHomeRepository
  participant GitHubPullRequestAPI
  GitHubActions->>GitHubActions: Resolve target version
  GitHubActions->>PyPI: Poll aioesphomeapi version
  PyPI-->>GitHubActions: Confirm indexed version
  GitHubActions->>GitHubApp: Create repository token
  GitHubActions->>ESPHomeRepository: Check out dev and update requirements.txt
  GitHubActions->>ESPHomeRepository: Commit and force-push branch
  GitHubActions->>GitHubPullRequestAPI: Update or create draft pull request
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the workflow that opens an ESPHome bump pull request when a release is published.
Description check ✅ Passed The description directly explains the workflow behavior, authentication, PyPI wait, pull request handling, and manual dispatch support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-esphome-bump-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/bump-esphome.yml:
- Around line 101-105: Update the workflow’s PR body generation to replace the
ESPHome PR reference placeholder with the PR number returned by gh pr create,
then write the updated body before editing or creating the PR. For the
existing-PR path, use the existing PR number in the replacement before invoking
gh pr edit; ensure both body-generation paths remove the placeholder.
- Around line 32-43: Update the PyPI polling loop in the “Wait for PyPI to index
the release” step to add curl connection and total transfer time limits using
--connect-timeout and --max-time, ensuring each request remains bounded within
the intended 45-minute retry window.
- Around line 16-17: Update the workflow containing the bump-esphome job to
serialize shared-branch updates with a workflow-level concurrency group named
for bump-aioesphomeapi and queue mode max. Add a stale-version check if releases
must be processed in order. Bound the PyPI polling curl request with connect and
total timeouts, and remove or replace the unresolved esphome PR-number
placeholder from generated PR bodies.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 41d316c4-d1ee-418c-aa74-11df32b30bc3

📥 Commits

Reviewing files that changed from the base of the PR and between 045117c and 54dbfd0.

📒 Files selected for processing (1)
  • .github/workflows/bump-esphome.yml

Comment on lines +16 to +17
bump-esphome:
runs-on: ubuntu-latest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file=".github/workflows/bump-esphome.yml"
if [ -f "$file" ]; then
  wc -l "$file"
  cat -n "$file"
else
  echo "Missing file: $file"
  git ls-files | rg '(^|/)bump-esphome\.yml$|bump-aioesphomeapi|pull_request_template'
fi

printf '\nRelated references:\n'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' \
  'bump-aioesphomeapi|concurrency:|force-push|workflow_dispatch|pull request|pull_request|aioesphomeapi' \
  .github README.md 2>/dev/null || true

Repository: esphome/aioesphomeapi

Length of output: 10886


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

p = Path(".github/workflows/bump-esphome.yml")
text = p.read_text()

checks = {
    "has_concurrency": bool(re.search(r"(?m)^concurrency\s*:", text)),
    "checks_out_dev": bool(re.search(r"(?m)^\s*ref:\s*dev\s*$", text)),
    "creates_shared_branch": "git checkout -b bump-aioesphomeapi" in text,
    "force_pushes_shared_branch": "git push --force origin bump-aioesphomeapi" in text,
    "edits_shared_pr": "gh pr edit" in text,
    "lists_open_shared_pr": "--head bump-aioesphomeapi --state open" in text,
    "curl_has_timeout": bool(re.search(r"(?m)^\s*if curl\b[^\n]*--max-time|^\s*if curl\b[^\n]*--connect-timeout", text)),
}
for name, value in checks.items():
    print(f"{name}={value}")

print("poll_attempts=90")
print("sleep_seconds=30")
PY

Repository: esphome/aioesphomeapi

Length of output: 488


🌐 Web query:

site:docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions concurrency pending one running one pending cancel-in-progress

💡 Result:

The terms "one running," "one pending," and "cancel-in-progress" refer to the concurrency control mechanisms used in GitHub Actions workflows [1]. When you define a concurrency group, GitHub Actions ensures that only one workflow or job with that specific key is in progress at any given time [1]. The behavior of queued jobs depends on your configuration: One Running, One Pending: By default, when a workflow or job with a concurrency group is already running, any new job with the same concurrency key will enter a "pending" state [1]. It waits until the currently running job completes. By default, if another job is already "pending" in that group, it will be canceled to make room for the newest job [1]. Cancel-in-Progress: If you set cancel-in-progress: true within your concurrency configuration, GitHub Actions will automatically cancel any existing job or workflow that is currently running in the same concurrency group when a new job with that key is triggered [1]. This effectively stops the older task to allow the newer one to take its place immediately, rather than waiting for the previous one to finish [1]. You can define this in your workflow file as follows: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true This configuration ensures that only the latest version of the job or workflow runs for a given reference or branch [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

p = Path(".github/workflows/bump-esphome.yml")
text = p.read_text()

checks = {
    "has_concurrency": bool(re.search(r"(?m)^concurrency\s*:", text)),
    "checks_out_dev": bool(re.search(r"(?m)^\s*ref:\s*dev\s*$", text)),
    "creates_shared_branch": "git checkout -b bump-aioesphomeapi" in text,
    "force_pushes_shared_branch": "git push --force origin bump-aioesphomeapi" in text,
    "edits_shared_pr": "gh pr edit" in text,
    "lists_open_shared_pr": "--head bump-aioesphomeapi --state open" in text,
    "curl_has_timeout": bool(re.search(
        r"(?m)^\s*if curl\b[^\n]*(?:--max-time|--connect-timeout)", text
    )),
}
for name, value in checks.items():
    print(f"{name}={value}")

print("poll_attempts=90")
print("sleep_seconds=30")
PY

Repository: esphome/aioesphomeapi

Length of output: 488


🏁 Script executed:

#!/bin/bash
set -eu

file=".github/workflows/bump-esphome.yml"
printf 'has_concurrency='
if grep -Eq '^concurrency[[:space:]]*:' "$file"; then echo true; else echo false; fi

printf 'checks_out_dev='
if grep -Eq '^[[:space:]]+ref:[[:space:]]*dev[[:space:]]*$' "$file"; then echo true; else echo false; fi

printf 'creates_shared_branch='
if grep -Fq 'git checkout -b bump-aioesphomeapi' "$file"; then echo true; else echo false; fi

printf 'force_pushes_shared_branch='
if grep -Fq 'git push --force origin bump-aioesphomeapi' "$file"; then echo true; else echo false; fi

printf 'updates_shared_pr='
if grep -Fq -- '--head bump-aioesphomeapi --state open' "$file" &&
   grep -Fq 'gh pr edit' "$file"; then echo true; else echo false; fi

printf 'poll_attempts='
sed -n '36p' "$file" | sed -E 's/.*seq 1 ([0-9]+).*/\1/'
printf 'sleep_seconds='
sed -n '40p' "$file" | sed -E 's/.*sleep ([0-9]+).*/\1/'

Repository: esphome/aioesphomeapi

Length of output: 325


🌐 Web query:

site:docs.github.com/actions concurrency "queue" "max" GitHub Actions

💡 Result:

In GitHub Actions, the maximum number of jobs or workflow runs that can be in a pending state within a concurrency group is determined by the queue property [1][2]. When using the concurrency keyword in your workflow, you can set the queue property as follows: * single (default): At most one job or workflow run can be in a pending state at a time [1][2]. If a new job is queued, the existing pending job is canceled and replaced [1][2]. * max: Up to 100 jobs or workflow runs can be in a pending state at a time [1][2]. Once this queue limit of 100 is reached, any additional jobs or workflow runs are canceled [1][2]. Note that the queue: max configuration cannot be combined with cancel-in-progress: true, as they represent conflicting strategies for handling concurrent runs [1][2]. If you attempt to use them together, it will cause a workflow validation error [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

file=".github/workflows/bump-esphome.yml"
printf 'has_concurrency_matches='; grep -Ec '^concurrency[[:space:]]*:' "$file" || true
printf 'checkout_dev_matches='; grep -Ec '^[[:space:]]+ref:[[:space:]]*dev[[:space:]]*$' "$file" || true
printf 'shared_branch_creation_matches='; grep -Fc 'git checkout -b bump-aioesphomeapi' "$file" || true
printf 'shared_force_push_matches='; grep -Fc 'git push --force origin bump-aioesphomeapi' "$file" || true
printf 'shared_pr_lookup_matches='; grep -Fc -- '--head bump-aioesphomeapi --state open' "$file" || true
printf 'pr_edit_matches='; grep -Ec '^[[:space:]]*gh pr edit ' "$file" || true
printf 'curl_timeout_options='; grep -Eo -- '--(max-time|connect-timeout|retry-max-time)[[:space:]]+[0-9]+' "$file" | tr '\n' ' '; echo

printf '\nTemplate:\n'
cat -n .github/PULL_REQUEST_TEMPLATE.md

printf '\nBody replacements:\n'
grep -nE 'template\.replace|Quick description|Other|api\.proto|linked pull request' "$file" .github/PULL_REQUEST_TEMPLATE.md || true

Repository: esphome/aioesphomeapi

Length of output: 2695


🌐 Web query:

site:docs.github.com/actions "Concurrency group ordering is not guaranteed" OR "ordering is not guaranteed" concurrency

💡 Result:

In the context of GitHub Actions, the statement that "concurrency group ordering is not guaranteed" refers to how jobs or workflow runs within the same concurrency group are processed [1][2]. While GitHub Actions processes jobs or workflow runs in the same concurrency group using a first-in-first-out (FIFO) approach based on the time each one starts waiting on the concurrency group, the actual execution order is not guaranteed [1][2]. This is because the specific start time of a job or run can vary due to infrastructure factors [1][2]. For clarity, the concurrency group feature allows you to define a string or expression to ensure that only a single job or workflow with that specific key runs at any given time [2]. If new jobs or workflow runs are triggered with the same concurrency key, the system places them into a queue (or cancels existing ones depending on the configuration) [2]. The "not guaranteed" aspect strictly pertains to the precise execution sequence of these queued items [1][2].

Citations:


Serialize shared-branch updates and bound PyPI polling.

  • Add a workflow-level concurrency group for bump-aioesphomeapi with queue: max. Concurrency does not guarantee release order, so add a stale-version check if every release must be processed in order.
  • Add --connect-timeout and --max-time to the PyPI curl request. The current polling loop does not bound a stalled request.
  • Remove or replace the unresolved esphome/esphome#<esphome PR number goes here> placeholder in generated PR bodies.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml around lines 16 - 17, Update the workflow
containing the bump-esphome job to serialize shared-branch updates with a
workflow-level concurrency group named for bump-aioesphomeapi and queue mode
max. Add a stale-version check if releases must be processed in order. Bound the
PyPI polling curl request with connect and total timeouts, and remove or replace
the unresolved esphome PR-number placeholder from generated PR bodies.

Comment thread .github/workflows/bump-esphome.yml
Comment thread .github/workflows/bump-esphome.yml Outdated
Comment on lines +101 to +105
body = template.replace(
"<!-- Quick description and explanation of changes -->", description, 1
)
body = body.replace("- [ ] Other", "- [x] Other", 1)
Path("pr_body.md").write_text(body)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fill the required ESPHome PR reference.

The generated body replaces only the description and Other checklist item. It leaves esphome/esphome#<esphome PR number goes here> in the target template.

After gh pr create returns its PR number, replace the placeholder and update the body. For an existing PR, use $existing before gh pr edit.

Also applies to: 132-137

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml around lines 101 - 105, Update the
workflow’s PR body generation to replace the ESPHome PR reference placeholder
with the PR number returned by gh pr create, then write the updated body before
editing or creating the PR. For the existing-PR path, use the existing PR number
in the replacement before invoking gh pr edit; ensure both body-generation paths
remove the placeholder.

@esphbot

esphbot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

PR Review — Add workflow to open esphome bump PR on release

Well-built workflow — one blocking gap (missing concurrency on a shared force-pushed branch), rest are hardening nits.

The security and plumbing work here is genuinely careful, and several things I expected to be wrong check out:

  • permissions: {} at workflow level, a narrowly scoped app token (repositories: esphome, contents+PR write only), and every action pinned to a full SHA.
  • No template-injection surface: github.event.release.tag_name and inputs.version both go through env: rather than being interpolated into run: bodies.
  • I verified the pieces that depend on the other repo's shape rather than assuming them: esphome dev's requirements.txt really does carry a bare aioesphomeapi==45.8.0 and the sed -n extractor returns 45.8.0 against the live file; both template anchors (<!-- Quick description... -->, - [ ] Other) exist in esphome's current PR template; and gh pr create does work outside a git checkout when --repo/--base/--head are supplied (confirmed with --dry-run in a non-git dir), which is the one thing that would have broken the final step outright.
  • The 45-minute PyPI budget is correctly sized — release runs measure 13–20 min.

What needs attention:

  • 🟡 No concurrency group. Every run force-pushes the same branch and races gh pr create. Release gaps of 23 min (v45.5.0→v45.5.1) and 37 min (v45.3.0→v45.3.1) are in this repo's history against a ~13–20 min in-flight window, so overlap is near-threshold, and ordering out of the poll loop is decided by wheel-build luck. The older run winning leaves esphome dev pinned to a superseded version in a plausible-looking draft PR.
  • 🟢 git push --force recreates the branch from dev, so esphome-side fixes a maintainer pushed onto the open bump PR are silently wiped; --force-with-lease after a fetch avoids it.
  • 🟢 Template substitution no-ops silently if esphome's template drifts, and leaves the esphome.io / developers.esphome.io / fixes <link to issue> placeholders raw in every generated body.
  • 🟢 gh pr list --head is unscoped (no --base, no --limit); multiple rows make $existing multi-line and break gh pr edit.
  • 🟢 sed -i "s/^aioesphomeapi==.*/..." overwrites the whole line, dropping any future trailing comment or environment marker on that pin.
  • 🟢 Pre-release publishes would auto-bump dev to an rc; a one-line job if: guards it.
  • 🟢 Poll curl has no --max-time and the job has no timeout-minutes, so the 45-min budget is a floor, not a ceiling.

Note on prior bot feedback: @coderabbitai's suggestion to substitute "the ESPHome PR number" into the body is off-target — those placeholders in esphome's template are for documentation PRs (esphome.io / developers.esphome.io), not for the bump PR's own number. The "Pull request in esphome" placeholder it has in mind lives in this repo's template, which is not the one being rendered.


🟡 Important

1. No concurrency group — back-to-back releases race the shared branch and can leave esphome pinned to the older version
.github/workflows/bump-esphome.yml:13

The workflow has no concurrency: block, yet every run force-pushes the same bump-aioesphomeapi branch and races on gh pr create. The PR description explicitly calls out "back to back releases update the existing PR" — that is exactly the path with no serialization.

This is not hypothetical for this repo. I checked the actual numbers:

  • Release runs take ~13–20 min end-to-end (gh run list --workflow=release.yml: 45.8.0 = 16 min, 45.6.0 = 20 min, 45.5.1 = 13 min).
  • This workflow sits in its PyPI poll for up to 45 min, so a run stays alive for roughly the whole release-build window.
  • Observed release gaps: v45.5.0 → v45.5.1 was 23 minutes, v45.3.0 → v45.3.1 was 37 minutes. Both land inside or right at the edge of an in-flight run.

Two overlapping runs give two bad outcomes:

  • Wrong pin wins. Run order out of the poll loop is decided by which release's wheel matrix finishes first, not by which tag is newer (macOS/QEMU runner queueing swings build time by 10+ min). If the older run pushes last, esphome/esphome gets a draft PR titled "Bump aioesphomeapi from X to 45.5.0" while 45.5.1 — usually the patch that prompted the second release — is what should land. A maintainer merging that draft ships esphome dev on a superseded dependency until the next release corrects it.
  • Spurious job failure. Both runs can see no open PR and both call gh pr create; the loser fails with "a pull request already exists", putting a red X on a release run.

Fix — add at workflow level:

concurrency:
  group: bump-aioesphomeapi
  cancel-in-progress: false

Belt-and-braces, since the old pin is read from dev and not from the existing branch: after computing old, also refuse to push when $VERSION is not the newest of the two candidates (e.g. [[ "$(printf '%s\n%s\n' "$old" "$VERSION" | sort -V | tail -1)" == "$VERSION" ]]), so a straggler can never downgrade a branch a newer run already updated.

permissions: {}

🟢 Suggestions

1. Unconditional `git push --force` discards commits a human added to the open bump PR
.github/workflows/bump-esphome.yml:122

The branch is always recreated from a fresh dev (git checkout -b bump-aioesphomeapi at line 120) and then force-pushed. Anything on the remote branch is destroyed, not just the previous bot commit.

The realistic scenario is not a bystander typo-fixing: an aioesphomeapi bump sometimes needs matching esphome-side changes (a renamed model field, a new enum). A maintainer pushes those onto the open draft PR, the next release fires, and the workflow silently wipes them — the PR shows only the one-line requirements bump again, with the fixes gone from the branch.

Suggested fix: fetch the remote branch first and use --force-with-lease, so the push aborts when the remote moved since the run started.

git fetch origin bump-aioesphomeapi || true
git push --force-with-lease=bump-aioesphomeapi origin bump-aioesphomeapi

(--force-with-lease needs the remote-tracking ref to exist, hence the fetch — a bare --force-with-lease after checkout -b from dev will be rejected for missing lease info.)

This extends @Copilot's comment on the same line with the concrete loss case.

git checkout -b bump-aioesphomeapi
git commit -am "Bump aioesphomeapi from $OLD to $VERSION"
git push --force origin bump-aioesphomeapi
2. Template substitution is unguarded, and unrelated esphome placeholders are left raw in every generated body
.github/workflows/bump-esphome.yml:96-105

I verified both anchors currently exist in esphome/esphome@dev's .github/PULL_REQUEST_TEMPLATE.md<!-- Quick description and explanation of changes --> and a single - [ ] Other line — so the substitution works today. Two forward-looking problems:

1. Silent no-op on template drift. str.replace returns the input unchanged when the needle is missing. If esphome reworks its template (it is actively maintained — it already carries newer rows like "New developer-facing feature" and "Developer breaking change"), this workflow keeps succeeding and opens a PR whose body is the raw unfilled template with no "Types of changes" box ticked. Failing loudly is cheap:

for needle, repl in ((DESC_MARKER, description), ("- [ ] Other", "- [x] Other")):
    if needle not in body:
        raise SystemExit(f"PR template no longer contains {needle!r}")
    body = body.replace(needle, repl, 1)

2. Leftover placeholders. Everything the workflow does not touch ships verbatim: - fixes <link to issue>, - esphome/esphome.io#<esphome.io PR number goes here>, - esphome/developers.esphome.io#<developers.esphome.io PR number goes here>, the empty config.yaml fence, and all eight unticked Test Environment boxes. Every auto-opened bump PR will carry that noise. Consider stripping the sections that can never apply to a dependency bump.

On @coderabbitai's related comment: its suggestion to substitute "the ESPHome PR number" into the body does not apply here. Those placeholders in esphome's template are for esphome.io / developers.esphome.io documentation PRs, not for the bump PR's own number — filling them with the created PR number would produce a wrong cross-reference. The "Pull request in esphome" placeholder CodeRabbit is thinking of belongs to this repo's template, which is not the one being rendered.

body = template.replace(
    "<!-- Quick description and explanation of changes -->", description, 1
)
body = body.replace("- [ ] Other", "- [x] Other", 1)
3. `gh pr list --head` is unscoped — multiple results break `gh pr edit`
.github/workflows/bump-esphome.yml:132-134

--jq '.[].number' emits one number per line and --limit defaults to 30, so $existing can be multi-line; gh pr edit "$existing" then gets a newline-joined argument and fails, losing the body/title update.

Two ways to get more than one row:

  • The REST head filter matches by ref name alone when no owner: prefix is given, so an unrelated fork PR into esphome/esphome whose head branch is also named bump-aioesphomeapi matches.
  • A second open PR from the same branch to a different base (e.g. a release branch) also matches, since --base is not constrained.

Deterministic version:

existing=$(gh pr list --repo esphome/esphome \
  --head bump-aioesphomeapi --base dev --state open \
  --limit 1 --json number --jq '.[0].number // empty')

This is @Copilot's line-134 comment; agreeing and giving the concrete filter.

existing=$(gh pr list --repo esphome/esphome --head bump-aioesphomeapi --state open --json number --jq '.[].number')
4. `sed` replaces the whole line, silently dropping any trailing comment or environment marker
.github/workflows/bump-esphome.yml:80

s/^aioesphomeapi==.*/aioesphomeapi==$VERSION/ overwrites everything after the package name, not just the version.

esphome's requirements.txt uses trailing metadata heavily on neighbouring lines — tzlocal==5.4.4 # from time, aiohappyeyeballs==2.7.1 # Happy Eyeballs for requests downloads; already pulled in by aioesphomeapi, and cryptography==...; platform_system != "Darwin" markers. The aioesphomeapi line happens to be bare today (I checked dev), so this is latent — but the first time anyone annotates it, the next release silently deletes the annotation or the environment marker as part of an otherwise-innocuous bump PR.

Anchor the replacement to just the version token:

sed -i -E "s/^aioesphomeapi==[^[:space:];#]+/aioesphomeapi==$VERSION/" requirements.txt
sed -i "s/^aioesphomeapi==.*/aioesphomeapi==$VERSION/" requirements.txt
5. Pre-releases would auto-bump esphome's `dev` pin
.github/workflows/bump-esphome.yml:4-5

release: types: [published] fires for pre-releases too, and release.yml publishes them to PyPI, so the poll would succeed and this workflow would open a PR pinning esphome dev to an rc/beta.

The repo has never cut a pre-release (no rc/a/b tags in git tag), so this is latent rather than live — but the version regex at line 70 ([0-9a-zrcb.]) suggests pre-release strings were at least contemplated, and the guard is one line:

jobs:
  bump-esphome:
    if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease

Adding github.repository == 'esphome/aioesphomeapi' to the same condition also stops the job from starting (and failing on the missing app secret) whenever a fork publishes a release.

on:
  release:
    types: [published]
6. Poll loop's 45-minute budget is not actually bounded
.github/workflows/bump-esphome.yml:36-41

curl here has no --connect-timeout / --max-time, so the intended 90 × 30s = 45 min ceiling is only a floor: a blackholed connection makes each iteration cost the OS TCP timeout on top of the sleep. With no timeout-minutes on the job either, the fallback is the 6-hour default — a release run that sits amber for hours instead of failing.

The budget itself looks right: release builds measured at 13–20 min, so 45 min has healthy headroom. Just bound the requests and the job:

    timeout-minutes: 60
if curl -sfo /dev/null --connect-timeout 10 --max-time 20 "https://pypi.org/pypi/aioesphomeapi/$VERSION/json"; then
for _ in $(seq 1 90); do
  if curl -sfo /dev/null "https://pypi.org/pypi/aioesphomeapi/$VERSION/json"; then
    exit 0
  fi
  sleep 30
done

Checklist

  • No hardcoded secrets; least-privilege token scoping
  • No shell/template injection from untrusted inputs
  • Third-party actions pinned to immutable SHAs
  • Concurrent/repeated invocations are safe — warning #1, suggestion #1
  • External-file parsing degrades loudly, not silently — suggestion #2, suggestion #4
  • Command output parsed deterministically — suggestion #3
  • Trigger scoped to intended events — suggestion #5
  • Network waits and job runtime are bounded — suggestion #6
  • Diff matches PR description; no scope creep

To rebase and address feedback, mention me: @esphbot rebase critical (fixes 🔴 only), @esphbot rebase important (fixes 🔴 + 🟡), or @esphbot rebase --fix for all. (A bare @esphbot rebase only rebases onto the base branch.)


Automated review by Kōan (Claude) HEAD=54dbfd0 7 min 10s

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Important issues found.

  • No concurrency group — back-to-back releases race the shared branch and can leave esphome pinned to the older version

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

workflow=$(fd -a '^bump-esphome\.yml$' .github/workflows | head -n1)
if [ -z "$workflow" ]; then
  echo "Workflow not found"
  exit 1
fi

echo "== Workflow outline =="
ast-grep outline "$workflow" --items all || true

echo "== Relevant workflow lines =="
rg -n -C 8 'PULL_REQUEST_TEMPLATE|template|replace\(|gh pr (create|edit|list)|bump-aioesphomeapi|concurrency|curl|sed -i' "$workflow"

echo "== CodeRabbit review comments =="
gh api 'repos/esphome/aioesphomeapi/pulls/1862/comments?per_page=100' \
  --jq '.[] | select(.user.login == "coderabbitai") | {id, path, line, body}'

Length of output: 4590


@esphbot, the workflow confirms your clarification.

bump-esphome.yml reads esphome/.github/PULL_REQUEST_TEMPLATE.md at Line 96. It only replaces the quick-description marker and the Other checkbox at Lines 101–104. It does not read this repository’s PR template.

The documentation placeholders must remain unrelated to the generated bump PR number. No CodeRabbit inline review comment was returned for PR #1862, so there is no separate inline comment to resolve.

The shell output also confirms the remaining workflow hardening findings: no concurrency block, unconditional git push --force, unbounded curl, whole-line sed replacement, and unscoped gh pr list.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/bump-esphome.yml (2)

130-132: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Protect human commits on the shared branch.

git push --force can discard commits added to bump-aioesphomeapi after checkout, including fixes made while the draft PR is under review.

Fetch the remote branch first. Build on its tip or fail when it contains non-automation commits. Use an explicit --force-with-lease for the final update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml around lines 130 - 132, Update the
bump-aioesphomeapi workflow around the branch checkout and push to fetch the
remote branch first, preserve or validate commits added after checkout, and fail
if it contains non-automation commits. Replace the unconditional force push with
an explicit --force-with-lease using the fetched remote state for the final
update.

28-32: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use a PEP 440-aware version comparison.

The release job skips prereleases, but manual input accepts them. sort -V orders 45.8.0rc1 after 45.8.0, which can update or skip the wrong requirements.txt pin. Reject unsupported versions or compare them with packaging.version.Version.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml around lines 28 - 32, Update the version
handling around INPUT_VERSION and TAG_NAME to validate and compare versions
using PEP 440 semantics, such as packaging.version.Version, rather than sort -V.
Ensure prerelease inputs are handled consistently with release tags, and reject
unsupported or invalid version formats before modifying the requirements pin.

Source: MCP tools

🧹 Nitpick comments (1)
.github/workflows/bump-esphome.yml (1)

88-88: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Preserve trailing requirement metadata.

This replacement rewrites the complete line as aioesphomeapi==$VERSION. If the requirement later gains an environment marker, inline comment, or hash suffix, the workflow deletes it.

Replace only the version token and retain the suffix.

Proposed replacement
-          sed -i "s/^aioesphomeapi==.*/aioesphomeapi==$VERSION/" requirements.txt
+          sed -i -E "s|^(aioesphomeapi==)[^[:space:]]+(.*)$|\1$VERSION\2|" requirements.txt
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml at line 88, Update the sed command in the
workflow to replace only the aioesphomeapi version token, preserving any
trailing environment marker, inline comment, or hash suffix on the requirement
line. Keep the existing VERSION value and package matching behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/bump-esphome.yml:
- Around line 142-144: Update the existing PR lookup in the bump workflow to use
the REST API with an owner-qualified head filter,
`head=esphome:bump-aioesphomeapi`, and `per_page=1`; preserve extracting the
matching PR number into `existing` and editing it with `gh pr edit`.

---

Outside diff comments:
In @.github/workflows/bump-esphome.yml:
- Around line 130-132: Update the bump-aioesphomeapi workflow around the branch
checkout and push to fetch the remote branch first, preserve or validate commits
added after checkout, and fail if it contains non-automation commits. Replace
the unconditional force push with an explicit --force-with-lease using the
fetched remote state for the final update.
- Around line 28-32: Update the version handling around INPUT_VERSION and
TAG_NAME to validate and compare versions using PEP 440 semantics, such as
packaging.version.Version, rather than sort -V. Ensure prerelease inputs are
handled consistently with release tags, and reject unsupported or invalid
version formats before modifying the requirements pin.

---

Nitpick comments:
In @.github/workflows/bump-esphome.yml:
- Line 88: Update the sed command in the workflow to replace only the
aioesphomeapi version token, preserving any trailing environment marker, inline
comment, or hash suffix on the requirement line. Keep the existing VERSION value
and package matching behavior unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 38e039ce-5727-4a35-9036-fc1c387d11d1

📥 Commits

Reviewing files that changed from the base of the PR and between 54dbfd0 and b26f4bf.

📒 Files selected for processing (1)
  • .github/workflows/bump-esphome.yml

Comment on lines +142 to +144
existing=$(gh pr list --repo esphome/esphome --base dev --head bump-aioesphomeapi --state open --json number --jq '.[0].number // empty')
if [[ -n "$existing" ]]; then
gh pr edit "$existing" --repo esphome/esphome --title "$title" --body-file pr_body.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow context ---'
sed -n '1,180p' .github/workflows/bump-esphome.yml

printf '%s\n' '--- gh usage in workflow ---'
rg -n -C 3 'gh pr list|gh api|gh pr edit|bump-aioesphomeapi|git push' .github/workflows/bump-esphome.yml

Repository: esphome/aioesphomeapi

Length of output: 6221


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '1,180p' .github/workflows/bump-esphome.yml
rg -n -C 3 'gh pr list|gh api|gh pr edit|bump-aioesphomeapi|git push' .github/workflows/bump-esphome.yml

Repository: esphome/aioesphomeapi

Length of output: 6167


🌐 Web query:

GitHub CLI gh pr list --head branch name fork pull requests --repo semantics and GitHub REST list pull requests head owner:branch per_page

💡 Result:

For the GitHub CLI and GitHub REST API, filtering pull requests by head branch uses different syntax and capabilities: GitHub CLI (gh pr list) The gh pr list command uses the --head flag to filter pull requests by the head branch name [1][2]. Syntax: gh pr list --head [1][3] Important Limitation: As of the latest documentation, the --head flag does not support the username:branch syntax (e.g., --head owner:branch is not supported) [1][4][5]. Users should provide only the branch name [1][3]. GitHub REST API (List pull requests) The GitHub REST API endpoint GET /repos/{owner}/{repo}/pulls allows filtering by head branch using the head query parameter [6][7]. Syntax: head=: [6][7] Requirement: For cross-repository pull requests within the same network, you must namespace the head branch with the username (e.g., octocat:test-branch) [6][8][9]. Summary Comparison Feature | GitHub CLI (gh pr list --head) | GitHub REST API (head parameter) --- | --- | --- Supported Syntax | | : Namespace Support | Not supported | Required for cross-repo PRs Primary Use | Command line filtering | Programmatic API integration Sources: [1][4][5][6][7][3]

Citations:


Use an owner-qualified head filter.

gh pr list --head bump-aioesphomeapi can match pull requests from forks. If a fork pull request is returned first, gh pr edit updates the wrong pull request. Use the REST API with head=esphome:bump-aioesphomeapi and per_page=1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump-esphome.yml around lines 142 - 144, Update the
existing PR lookup in the bump workflow to use the REST API with an
owner-qualified head filter, `head=esphome:bump-aioesphomeapi`, and
`per_page=1`; preserve extracting the matching PR number into `existing` and
editing it with `gh pr edit`.

Source: MCP tools

@bdraco
bdraco merged commit 62c3f9b into main Aug 11, 2026
18 checks passed
@bdraco
bdraco deleted the add-esphome-bump-workflow branch August 11, 2026 16:54
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants