Skip to content

fix: exempt untimestamped update types from minimumReleaseAge, enable osvVulnerabilityAlerts - #1256

Open
trieloff wants to merge 1 commit into
mainfrom
fix-renovate-stability-days
Open

fix: exempt untimestamped update types from minimumReleaseAge, enable osvVulnerabilityAlerts#1256
trieloff wants to merge 1 commit into
mainfrom
fix-renovate-stability-days

Conversation

@trieloff

Copy link
Copy Markdown
Contributor

Summary

Keeps the 14-day quarantine from #1135 exactly as-is, but fixes a mechanism that
prevents it from ever going green, and adds a control that covers what a
release-age window structurally cannot.

Two changes:

  1. Exempt update types that cannot satisfy a release-age check (pin,
    pinDigest, bump, lockfileUpdate, lockFileMaintenance, rollback,
    replacement), plus relax digest to minimumReleaseAgeBehaviour: "timestamp-optional".
  2. Enable osvVulnerabilityAlerts.

The 14-day window itself is unchanged.

Why

external fixes and external major both set minimumReleaseAge: "14 days",
and external fixes matches ["patch", "pin", "digest", "minor"]. Renovate
documents pin as not supporting minimumReleaseAge
at all — there is no release timestamp to age — along with
lockFileMaintenance, lockfileUpdate, rollback, bump and replacement.
Renovate's own security:minimumReleaseAge* presets null these out
deliberately, and the source comment
states the consequence directly:

Certain update types don't currently support releaseTimestamps, so applying
minimumReleaseAge to them results in updates that will never come.

This matters more than it looks, because the renovate/stability-days status
check is computed per branch, not per package, and any single pending member
turns the whole branch yellow (lib/workers/repository/update/branch/index.ts):

config.stabilityStatus = 'green';
// Default to 'success' but set 'pending' if any update is pending
for (const upgrade of config.upgrades) {
  ...
  if (timeElapsed < minimumReleaseAgeMs) {
    config.stabilityStatus = 'yellow';
    continue;
  }

Since internalChecksAsSuccess defaults to false, a yellow stability check
blocks automerge. So one untimestamped update in a large group holds the entire
grouped PR unmergeable, and unlike a genuinely-too-fresh package, waiting never
clears it.

digest is a related but separate problem: it does have a timestamp at lookup
time, but the branch stage does not always carry it forward. That is
renovatebot/renovate#45236
(open, reproduced on main by a third party), whose reporter describes the
symptom as:

A permanently unresolvable yellow check on every digest and
lock-file-maintenance PR, which teaches reviewers to ignore the check entirely.
automerge cannot complete on those branches, because internalChecksAsSuccess
defaults to false. That failure is silent.

timestamp-optional is the workaround documented in that issue and in the
key-concepts FAQ.

Why osvVulnerabilityAlerts

A release-age window only helps if a malicious release is detected during the
window. osvVulnerabilityAlerts is orthogonal: it refuses updates to versions
OSV has flagged as malicious regardless of age, marking them
skipReason: malicious-update-proposed. It closes the gap where a compromise
goes unnoticed for longer than 14 days.

Note this does not delay security fixes. vulnerabilityAlerts already
defaults to minimumReleaseAge: null and groupName: null, so CVE fixes bypass
the quarantine and are never batched into the external groups.

Observed impact

adobe/helix-cli#2771 ("fix(deps): update external fixes") has been open since
2026-08-03 with:

renovate/stability-days = pending
  "Updates have not met minimum release age requirement"

while all real CI (CodeQL, Kodiak, WIP, Analyze, Test, Test (Windows)) is green
and only 3 of its 14 packages have any pending newer version. The PR is
mergeable; automerge simply never fires.

Test plan

  • default.json is valid JSON
  • Validates against renovate-schema.json
    via ajv — both before and after this change, so no schema regression
  • minimumReleaseAge confirmed as type: ["string", "null"] in the schema,
    so null is a legal value in a packageRule
  • minimumReleaseAgeBehaviour: "timestamp-optional" confirmed against the
    schema enum
  • osvVulnerabilityAlerts confirmed as a top-level boolean
  • After merge: confirm renovate/stability-days reaches green on a grouped
    external PR, and that automerge fires on schedule
  • After merge: confirm no pin/digest update sits pending indefinitely

Notes / not in this PR

Splitting external fixes into dev vs runtime dependencies is a plausible
follow-up: smaller groups go quiet more often, so the stability check reaches
green more often. That is a separate change and is deliberately not bundled here.

Renovate also reports grouped pending members are not surfaced in the Dependency
Dashboard's "Pending Status Checks"
(discussion #39778),
which is why this failure mode is hard to see from the dashboard alone.

The `external fixes` and `external major` groups apply
`minimumReleaseAge: "14 days"`, but several update types carry no usable
release timestamp, so Renovate can never mark them as having met the
requirement. Because the `renovate/stability-days` status check is computed
per branch and any single pending member turns the whole branch yellow, one
such update strands the entire grouped PR in a permanently pending state.
Since `internalChecksAsSuccess` defaults to false, that silently blocks
automerge indefinitely -- waiting does not clear it.

Null out `minimumReleaseAge` for the update types Renovate documents as
unsupported (pin, pinDigest, bump, lockfileUpdate, lockFileMaintenance,
rollback, replacement), mirroring the carve-outs in Renovate's own
`security:minimumReleaseAge*` presets, and relax `digest` to
`timestamp-optional` to work around renovatebot/renovate#45236.

Also enable `osvVulnerabilityAlerts`, which blocks updates to versions OSV
has flagged as malicious regardless of age. This covers the case a release-age
window structurally cannot: a compromised version that is never detected
within the quarantine period.

The 14-day window itself is unchanged.
Copilot AI lite review requested due to automatic review settings August 28, 2026 09:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants