Skip to content

Minimum Release Age

What is Minimum Release Age?

minimumReleaseAge is a feature that requires Renovate to wait for a specified amount of time before suggesting a dependency update.

The use of minimumReleaseAge is not to slow down fast releasing project updates, but to provide a means to reduce risk supply chain security risks.

For example, minimumReleaseAge=14 days would ensure that a package update is not suggested by Renovate until 14 days after its release, which allows plenty of time to allow security researchers and automated security tools to catch malicious intent in packages.

Note: Renovate will wait for the set duration to pass for each separate version. Renovate does not wait until the package has seen no releases for x time-duration(minimumReleaseAge).

When the time passed since the release is less than the set minimumReleaseAge: Renovate adds a "pending" status check to that update's branch. After enough days have passed: Renovate replaces the "pending" status with a "passing" status check.

Configuration options

The following configuration options can be used to enable and tune the functionality of Minimum Release Age:

FAQs

What happens if the datasource and/or registry does not provide a release timestamp, when using minimumReleaseAge?

Warning

Renovate 42 changes the behaviour detailed below. In Renovate 42, the absence of a release timestamp will be treated as if the release is not yet past the timestamp, which provides a safer default. Prior to Renovate 42, you can opt into this behaviour using minimumReleaseAgeBehaviour=timestamp-required (added in 41.150.0)

Consider that:

  • we have set minimumReleaseAge to apply to a given dependency
  • that dependency has 3 updates available
    • 2 of which have a release timestamp that has not yet passed
    • 1 of which does not have a release timestamp

The current behaviour in Renovate is that we will treat the dependency without a release timestamp as if it has passed the minimumReleaseAge, and will immediately suggest that dependency update.

Warning

This is counter-intuitive behaviour, which is why Renovate 42 changes this behaviour to be more predictable.

What happens when an update is not yet passing the minimum release age checks?

For this question, consider two key aspects to Renovate updating a dependency:

Based on the values of internalChecksFilter and prCreation, different behaviour will occur.

internalChecksFilter=strict

If you have not configured internalChecksFilter, Renovate will use internalChecksFilter=strict as the default.

This will make sure that branches are not created if the minimumReleaseAge status check, renovate/stability-days, does not pass.

Debug logs example when internalChecksFilter=strict
DEBUG: Branch renovate/actions-checkout-5.x creation is disabled because internalChecksFilter was not met (repository=..., branch=renovate/actions-checkout-5.x)

In this case, no branch is created.

Warning

However, depending on the value of prCreation, the branch may still be created, even if it's pending status checks.

prCreation=immediate (default)

If you have not configured prCreation, Renovate will use prCreation=immediate as the default.

If an update is pending the minimum release age checks, Renovate will create a PR. When raising this PR, a renovate/stability-days status check will be added to the branch, marked as pending.

Renovate will not perform automerge until that check has passed.

If you do not wish Renovate to raise a PR until this status check has passed, you will want to set prCreation=not-pending, for instance using a package rule targeting the datasource you're enforcing this functionality on.

prCreation=not-pending

If an update is pending the minimum release age checks, Renovate will not create a branch, nor raise a PR.

If you have a Dependency Dashboard enabled, it will be found in the Dependency Dashboard in the "Pending Status Checks".

You can force the dependency update by requesting it via the Dependency Dashboard, or if you are self-hosting, you can use the checkedBranches to force the branch creation.

The recommendation is to set internalChecksFilter=strict and prCreation=not-pending when using minimumReleaseAge, so Renovate will create neither branches nor PRs on updates that haven't yet met minimum release age checks.

Which update types take minimumReleaseAge into account?

Depending on your manager, datasource and the given package(s), it may be that some updates provide a release timestamp that can have minimumReleaseAge enforced.

It's most likely the case that major, minor, and patch update types will have a corresponding minimumReleaseAge.

Generally, Renovate does not provide release timestamps for digest updates.

You can validate which update types may have release timestamps by following something similar to how verify if the registry you're using.

What happens to security updates?

Security updates bypass any minimumReleaseAge checks, and so will be raised as soon as Renovate detects them.

What happens to transitive dependencies?

Renovate does not currently manage any transitive dependencies - instead leaving that to package managers and lockFileMaintenance.

How do I opt out dependencies from minimum release age checks?

To opt out a dependency from minimum release age checks, create a package rule with minimumReleaseAge=null:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    // for instance
    "security:minimumReleaseAgeNpm",
  ],
  "packageRules": [
    {
      "description": "Disable minimum release age checks for internal dependencies",
      "matchPackageNames": ["@super-secret-organisation/*"],
      "minimumReleaseAge": null,
    },
  ],
}

Which datasources support release timestamps?

Tip

You can confirm if your datasource supports the release timestamp by viewing the documentation for the given datasource.

The datasource that Renovate uses must have a release timestamp for the minimumReleaseAge config option to work. Some datasources may have a release timestamp, but in a format Renovate does not support. In those cases a feature request needs to be implemented.

Note that you will also need to verify if the registry you're using provides the release timestamp.

Which registries support release timestamps?

The below is a non-exhaustive list of public registries which support release timestamps:

Datasource Registry URL Supported Notes
docker https://ghcr.io Issue
rubygems https://rubygems.org
docker https://index.docker.io
docker https://quay.io Issue
github-releases https://github.com
terraform-provider https://registry.terraform.io Not always returned
github-tags https://github.com
go https://proxy.golang.org,
golang-version https://raw.githubusercontent.com/golang/website
maven https://repo1.maven.org/maven2
node-version https://nodejs.org/dist
npm https://registry.npmjs.org
pypi https://pypi.org/pypi/
ruby-version https://www.ruby-lang.org

It is likely that if you are using a public registry (i.e. registry.npmjs.org, repo1.maven.org, etc) the release timestamp data will be present. We welcome user contributions to this table.

If you use a custom registry, for instance as a pull-through cache, additional configuration may be required.

If you are using a custom registry, or unsure about a public registry, you can confirm this using Renovate's debug logs by looking for the packageFiles with updates debug log line, which may contain a releaseTimestamp field in dependency updates:

packageFiles with updates debug log example
DEBUG: packageFiles with updates
{
  "baseBranch": "main",
  "config": {
    "dockerfile": [
      {
        "deps": [
          // NOTE that we're not seeing a release timestamp for this Docker digest
          {
            "depName": "ghcr.io/renovatebot/base-image",
            "packageName": "ghcr.io/renovatebot/base-image",
            "currentValue": "10.67.5",
            "currentDigest": "sha256:d67e849707f38e11c8674a59d3fffef1ea6977757f3a65d9d1a3a198bdd160cf",
            "replaceString": "ghcr.io/renovatebot/base-image:10.67.5@sha256:d67e849707f38e11c8674a59d3fffef1ea6977757f3a65d9d1a3a198bdd160cf",
            "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
            "datasource": "docker",
            "depType": "stage",
            "updates": [
              {
                "bucket": "major",
                "newVersion": "11.40.5",
                "newValue": "11.40.5",
                "newMajor": 11,
                "newMinor": 40,
                "newPatch": 5,
                "updateType": "major",
                "isBreaking": true,
                "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
                "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x"
              }
            ],
          }
      // ...
    ],
    "github-actions": [
      {
        "deps": [
          // NOTE that we get a release timestamp for this GitHub Action major version bump, as well as the current version's timestamp
          {
            "depName": "actions/setup-node",
            "commitMessageTopic": "{{{depName}}} action",
            "datasource": "github-tags",
            "versioning": "docker",
            "depType": "action",
            "replaceString": "actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0",
            "autoReplaceStringTemplate": "{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}",
            "currentValue": "v4.4.0",
            "currentDigest": "49933ea5288caeca8642d1e84afbd3f7d6820020",
            "updates": [
              {
                "bucket": "major",
                "newVersion": "v6.0.0",
                "newValue": "v6.0.0",
                "newDigest": "2028fbc5c25fe9cf00d9f06a71cc4710d4507903",
                "releaseTimestamp": "2025-10-14T02:37:06.000Z",
                "newVersionAgeInDays": 10,
                "newMajor": 6,
                "newMinor": 0,
                "newPatch": 0,
                "updateType": "major",
                "isBreaking": true,
                "libYears": 0.5323368531202435,
                "branchName": "renovate/main-actions-setup-node-6.x"
              }
            ],
            "packageName": "actions/setup-node",
            "warnings": [],
            "sourceUrl": "https://github.com/actions/setup-node",
            "registryUrl": "https://github.com",
            "mostRecentTimestamp": "2025-10-14T02:37:06.000Z",
            "isAbandoned": false,
            "currentVersion": "v4.4.0",
            "currentVersionTimestamp": "2025-04-02T19:20:51.000Z",
            "currentVersionAgeInDays": 204,
            "isSingleVersion": true,
            "fixedVersion": "v4.4.0"
          },

Given a log line such as ☝️ you can also use the following jq query to identify any dependencies (or their updates) that are missing the currentVersionTimestamp or releaseTimestamp fields like so:

jq query
# Code snippet licensed under the Apache-2.0, and co-authored-by: gpt-oss:20b
jq '
{
  # -------- missing currentVersionTimestamp ----------
  missingCurrentVersionTimestamps: [
    .config
    | to_entries[] as $ent
    | $ent.value[] as $group
    | $group.deps[] as $dep
    | select($dep.currentVersionTimestamp == null)
    | {
        manager: $ent.key,
        depName: $dep.depName,
        packageFile: $group.packageFile,
        datasource: $dep.datasource,
        registryUrls: (
          ($dep.registryUrl? | if . != null then [.] else [] end)
          + ($dep.registryUrls // [])
        )
      }
  ],
  # -------- missing releaseTimestamp in updates ----------
  missingReleaseTimestamps: [
    .config
    | to_entries[] as $ent
    | $ent.value[] as $group
    | $group.deps[] as $dep
    | select(any($dep.updates[]?; .releaseTimestamp == null))
    | {
        manager: $ent.key,
        depName: $dep.depName,
        packageFile: $group.packageFile,
        datasource: $dep.datasource,
        registryUrls: (
          ($dep.registryUrl? | if . != null then [.] else [] end)
          + ($dep.registryUrls // [])
        ),
        missingUpdates: [
          $dep.updates[]?
          | select(.releaseTimestamp == null)
          | . + {
              dependencyCurrentVersionTimestamp: $dep.currentVersionTimestamp,
              datasource: $dep.datasource
            }
        ]
      }
  ]
}
' debug-log.txt

Will then output:

jq query output
{
  "missingCurrentVersionTimestamps": [
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/containerbase/devcontainer",
      "packageFile": ".devcontainer/Dockerfile",
      "registryUrls": ["https://ghcr.io"]
    },
    {
      "manager": "renovate-config-presets",
      "datasource": null,
      "depName": "renovatebot/.github",
      "packageFile": "renovate.json",
      "registryUrls": []
    },
    {
      "manager": "regex",
      "datasource": "docker",
      "depName": "ghcr.io/containerbase/sidecar",
      "packageFile": "lib/config/options/index.ts",
      "registryUrls": ["https://ghcr.io"]
    }
  ],
  "missingReleaseTimestamps": [
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    },
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5-full",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:824737973a79d8c280f8ab1928017780fb936396dc83075a4f7770610eda37bd",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    },
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    }
  ]
}

Notice that this indicates that:

  • There are 3 dependencies that do not have a release timestamp, across different Managers and Datasources
  • There are 3 dependency updates, where neither the dependency nor the dependency update itself have a release timestamp

How do I add timestamp data to custom registries?

Renovate requires release timestamp to be provided by the registry.

A common solution is to point Renovate to a registry that does have the release timestamp in the form that Renovate is expecting. You can achieve this by using packageRules to prepend the public registry's URL to the registryUrls.

You can see exact examples below:

Maven datasource

For minimumReleaseAge to work, the Maven source must return reliable last-modified headers.

If your custom Maven source registry is pull-through and does not support the last-modified header, like GAR (Google Artifact Registry's Maven implementation) then you can extend the Maven source registry URL with https://repo1.maven.org/maven2 as the first item. Then the currentVersionTimestamp via last-modified will be taken from Maven central for public dependencies.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "packageRules": [
    {
      "matchDatasources": ["maven"],
      "registryUrls": [
        "https://repo1.maven.org/maven2",
        "https://europe-maven.pkg.dev/org-artifacts/maven-virtual"
      ]
    }
  ]
}

Pypi datasource

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "packageRules": [
    {
      "matchDatasources": ["pypi"],
      "registryUrls": [
        "https://pypi.org/pypi/",
        "https://custom-registry.example.com/pypi/some-repo/simple/"
      ]
    }
  ]
}