Skip to content

Commit 9fdac79

Browse files
sergeykadSergey
andauthored
fix(ci): stop Renovate silently aborting its whole run on every branch (#2283)
Renovate's stability status check needs commit-status write, which the App installation never granted. GitHub answers with 404 instead of 403, Renovate reads that as the repository having changed, and aborts the entire run right after writing its first branch: no PR, no dashboard update, nothing else processed. The abort logged below error level, so the workflow reported green through it. Two weeks of dependency updates were silently lost this way. - request commit-status write on the App token (installation permission granted separately, out of band) - promote the abort message to error so a recurrence fails the workflow - bump the pinned Renovate engine to 44.46.2 Co-authored-by: Sergey <sergey@example.com>
1 parent 26a63f8 commit 9fdac79

3 files changed

Lines changed: 57 additions & 2 deletions

File tree

.github/workflows/renovate.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ jobs:
4848
# dead: an installation token that lists any permission-* input drops
4949
# every permission it does not name, including vulnerability_alerts.
5050
permission-vulnerability-alerts: read
51+
# Renovate publishes a stability status check on every branch it
52+
# writes (minimumReleaseAge). Without commit-status access GitHub
53+
# answers 404 rather than 403, Renovate reads that as the repository
54+
# having changed, and aborts the WHOLE run right after writing its
55+
# first branch: no PR, no dependency dashboard, nothing else
56+
# processed. The app installation must grant this too; the token can
57+
# only ever narrow what the installation already has.
58+
permission-statuses: write
5159
permission-workflows: write
5260

5361
- name: Self-hosted Renovate
5462
uses: renovatebot/github-action@e09d604f8f803bb527bd8321ed5be06c460b8682 # v46.2.2
5563
with:
5664
configurationFile: renovate.json
57-
renovate-version: 44.26.0
65+
renovate-version: 44.46.2
5866
token: ${{ steps.app-token.outputs.token }}
5967
env:
6068
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}

renovate.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"matchMessage": "Git error - aborting",
1919
"newLogLevel": "error"
2020
},
21+
{
22+
"matchMessage": "Repository has changed during renovation - aborting",
23+
"newLogLevel": "error"
24+
},
2125
{
2226
"matchMessage": "/^Some release\\(s\\) did not have a releaseTimestamp, but as we're running with minimumReleaseAgeBehaviour=timestamp-optional, proceeding\\. See debug logs for more information$/",
2327
"newLogLevel": "info"
@@ -50,7 +54,7 @@
5054
"datasourceTemplate": "{{{datasource}}}"
5155
},
5256
{
53-
"description": "Vendored library pins. src/ha_mcp/_vendor/ holds third-party code copied verbatim from the pinned sdist, so no package manager sees it: enabledManagers excludes pip_requirements and dependabot's uv ecosystem reads only pyproject.toml/uv.lock. Without this manager a CVE fix would never open a PR. A bump alone does not regenerate the tree tests/src/unit/test_vendored_websockets.py fails the renovate PR until scripts/vendor_websockets.py is re-run and the result committed, which is the intended handoff.",
57+
"description": "Vendored library pins. src/ha_mcp/_vendor/ holds third-party code copied verbatim from the pinned sdist, so no package manager sees it: enabledManagers excludes pip_requirements and dependabot's uv ecosystem reads only pyproject.toml/uv.lock. Without this manager a CVE fix would never open a PR. A bump alone does not regenerate the tree \u2014 tests/src/unit/test_vendored_websockets.py fails the renovate PR until scripts/vendor_websockets.py is re-run and the result committed, which is the intended handoff.",
5458
"customType": "regex",
5559
"managerFilePatterns": [
5660
"/^src/ha_mcp/_vendor/requirements\\.txt$/"

tests/src/unit/test_supply_chain_workflow_shape.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,46 @@ def test_dev_release_tag_cleanup_uses_authenticated_github_api() -> None:
320320
in cleanup_run
321321
)
322322
assert "git push origin --delete" not in create_run + cleanup_run
323+
324+
325+
def test_renovate_can_write_the_status_check_it_publishes() -> None:
326+
"""Renovate must not abort its whole run right after writing a branch.
327+
328+
``minimumReleaseAge`` makes Renovate publish a stability status check on
329+
every branch it writes. GitHub answers a commit-status call from a token
330+
without that permission with 404, not 403, and Renovate reads 404 there as
331+
the repository having changed underneath it and aborts the ENTIRE run,
332+
before opening the PR, before updating the dependency dashboard, and
333+
before processing any other dependency. That is silent: the abort logs
334+
below error level, so the workflow still reports success.
335+
336+
This pins the workflow half only. The app installation must grant
337+
``statuses`` as well, which no test here can see: an installation token
338+
can only narrow the permissions the installation already holds.
339+
"""
340+
steps = _workflow(_WORKFLOW_DIR / "renovate.yml")["jobs"]["renovate"]["steps"]
341+
token_step = next(
342+
step for step in steps if "create-github-app-token" in str(step.get("uses", ""))
343+
)
344+
assert token_step["with"].get("permission-statuses") == "write", (
345+
"Renovate needs commit-status write, and a token listing any "
346+
"permission-* input drops every permission it does not name"
347+
)
348+
349+
350+
def test_a_renovate_abort_fails_the_workflow() -> None:
351+
"""A run that dies mid-way must not report success.
352+
353+
Renovate exits non-zero only when some record is logged at error level or
354+
above, so a fatal abort logged at info leaves the workflow green. This one
355+
hid two weeks of dead runs.
356+
"""
357+
config = json.loads((_REPO_ROOT / "renovate.json").read_text(encoding="utf-8"))
358+
promoted = {
359+
remap["matchMessage"]
360+
for remap in config["logLevelRemap"]
361+
if remap["newLogLevel"] == "error"
362+
}
363+
assert "Repository has changed during renovation - aborting" in promoted, (
364+
"this abort ends the whole repository run, so it cannot stay at info"
365+
)

0 commit comments

Comments
 (0)