Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/workflows/bump-esphome.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: Bump aioesphomeapi in esphome

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "aioesphomeapi version to bump to (without v prefix)"
required: true
type: string

permissions: {}

jobs:
bump-esphome:
runs-on: ubuntu-latest
Comment on lines +19 to +21

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.

steps:
- name: Determine version
id: version
env:
TAG_NAME: ${{ github.event.release.tag_name }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [[ -n "$INPUT_VERSION" ]]; then
version="$INPUT_VERSION"
else
version="${TAG_NAME#v}"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Wait for PyPI to index the release
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
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
echo "aioesphomeapi $VERSION never appeared on PyPI" >&2
exit 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- name: Create GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
owner: esphome
repositories: esphome
permission-contents: write
permission-pull-requests: write

- name: Checkout esphome/esphome
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: esphome/esphome
ref: dev
token: ${{ steps.app-token.outputs.token }}
path: esphome

- name: Update requirements.txt pin
id: bump
env:
VERSION: ${{ steps.version.outputs.version }}
working-directory: esphome
run: |
old=$(sed -n 's/^aioesphomeapi==\([0-9a-zrcb.]*\).*/\1/p' requirements.txt)
if [[ -z "$old" ]]; then
echo "aioesphomeapi pin not found in requirements.txt" >&2
exit 1
fi
if [[ "$old" == "$VERSION" ]]; then
echo "requirements.txt already pins aioesphomeapi==$VERSION"
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
sed -i "s/^aioesphomeapi==.*/aioesphomeapi==$VERSION/" requirements.txt
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "old=$old" >> "$GITHUB_OUTPUT"

- name: Build PR body from template
if: steps.bump.outputs.changed == 'true'
env:
OLD: ${{ steps.bump.outputs.old }}
VERSION: ${{ steps.version.outputs.version }}
run: |
python3 - <<'EOF'
import os
from pathlib import Path

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 +104 to +117

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.

EOF

- name: Commit and push
if: steps.bump.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
OLD: ${{ steps.bump.outputs.old }}
VERSION: ${{ steps.version.outputs.version }}
working-directory: esphome
run: |
bot_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
git config user.name "${APP_SLUG}[bot]"
git config user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com"
git checkout -b bump-aioesphomeapi
git commit -am "Bump aioesphomeapi from $OLD to $VERSION"
git push --force origin bump-aioesphomeapi
Comment on lines +132 to +134

- name: Open draft PR
if: steps.bump.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
OLD: ${{ steps.bump.outputs.old }}
VERSION: ${{ steps.version.outputs.version }}
run: |
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 +143 to +146
Comment on lines +142 to +144

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

else
gh pr create --repo esphome/esphome --base dev --head bump-aioesphomeapi \
--draft --title "$title" --body-file pr_body.md
fi
Loading