This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
commit 072d473f123c93dca9b3187d3df6dd711c07efaf Author: Szymon Janc <[email protected]> AuthorDate: Wed Oct 21 13:47:12 2026 +0200 ci: Mark inactive pull requests as stale Pull requests without activity for 12 months are marked with 'stale' label by actions/stale, run monthly, with a comment asking for rebase if still valid. Stale pull requests are never closed automatically and label is removed by next run after any update or comment. PR maintenance skips pull requests without activity for 12 months. As adding stale label updates pull request, those with the label are also skipped as long as head commit is older than 12 months, so a rebased pull request is handled without waiting for next stale run. Assisted-by: Claude:claude-opus-5-5 --- .github/scripts/pr_maintenance.py | 24 +++++++++++++++- .github/workflows/stale.yml | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/.github/scripts/pr_maintenance.py b/.github/scripts/pr_maintenance.py index 20f0b4dfd..84f49039f 100644 --- a/.github/scripts/pr_maintenance.py +++ b/.github/scripts/pr_maintenance.py @@ -30,6 +30,10 @@ is done through GitHub API using gh tool. - Pull requests with new head commit are reported for path based labeling, which is done by actions/labeler in the workflow. +Pull requests without activity for MAX_AGE_DAYS are skipped, also +when marked with 'stale' label (set by stale workflow) until new commit +is pushed. + 'Check workflows' status also marks pull request head as processed: it is tied to head commit and its description contains base branch workflows tree it was checked against, so only new or changed pull @@ -49,8 +53,12 @@ import sys CI_LABEL = "needs-ci-approval" WORKFLOWS_LABEL = "workflows-update" STATUS_CONTEXT = "Check workflows" +STALE_LABEL = "stale" SKIPPED = f"Skipped, '{WORKFLOWS_LABEL}' label present" +# Keep in sync with days-before-pr-stale in stale.yml +MAX_AGE_DAYS = 365 + # Runs never approved stay waiting forever, skip old ones CI_RUNS_MAX_AGE_DAYS = 30 @@ -83,6 +91,7 @@ query ( } nodes { number + updatedAt headRefOid baseRefName labels(first: 100) { @@ -99,6 +108,7 @@ query ( nodes { commit { oid + committedDate status { context(name: $context) { state @@ -160,11 +170,21 @@ def list_pulls(): "-f", f"query={PULLS_QUERY}", "-f", f"owner={owner}", "-f", f"repo={repo}", "-f", f"context={STATUS_CONTEXT}")) + since = (date.today() - timedelta(days=MAX_AGE_DAYS)).isoformat() pulls = [] + skipped = 0 for page in pages: for node in page["data"]["repository"]["pullRequests"]["nodes"]: + labels = [l["name"] for l in node["labels"]["nodes"]] commit = node["commits"]["nodes"][0]["commit"] + # Adding stale label updates pull request, stale workflow + # removes label only on its next run but rebase is new commit + if node["updatedAt"] < since or \ + (STALE_LABEL in labels and commit["committedDate"] < since): + skipped += 1 + continue + # Commits list is limited, skip pull request if head is missing if commit["oid"] != node["headRefOid"]: print(f"#{node['number']}: head commit not found, skipping") @@ -174,12 +194,14 @@ def list_pulls(): pulls.append({ "number": node["number"], "head": commit["oid"], - "labels": [l["name"] for l in node["labels"]["nodes"]], + "labels": labels, "status": (commit["status"] or {}).get("context"), "base_name": node["baseRefName"], "base_head": base.get("oid"), }) + print(f"{skipped} stale pull requests skipped") + trees = workflows_trees([p["head"] for p in pulls] + [p["base_head"] for p in pulls if p["base_head"]]) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..342624018 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Marks pull requests without activity for 12 months as stale. Stale pull +# requests are never closed, stale label is removed by next run after any +# update or comment. Pull request maintenance workflow skips stale pull +# requests until new commit is pushed. + +name: Stale pull requests + +on: + schedule: + - cron: '30 2 1 * *' + workflow_dispatch: + +permissions: + # Required for storing action state between runs + actions: write + issues: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }} + +jobs: + stale: + if: github.repository == 'apache/mynewt-core' + runs-on: ubuntu-slim + steps: + - uses: actions/stale@v11 + with: + # Keep in sync with MAX_AGE_DAYS in pr_maintenance.py + days-before-pr-stale: 365 + days-before-pr-close: -1 + days-before-issue-stale: -1 + days-before-issue-close: -1 + stale-pr-label: stale + stale-pr-message: >- + This pull request had no activity for 12 months and is marked + as stale. If it is still valid, please rebase it on current + base branch. Stale pull requests are not closed automatically, + the label is removed on any update or comment. + operations-per-run: 200
