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.

In other ecosystems and package managers, this may be referred to as a "dependency cooldown".

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

Where does the release timestamp need to be set?

To prevent supply-chain attacks, Renovate requires that the registry/datasource provides the timestamp.

This ensures that a package maintainer cannot specify (maliciously or accidentally) a different timestamp to when the package was actually published.

Warning

For instance, a suggestion for the Docker ecosystem is to use the org.opencontainers.image.created image annotation.
Unfortunately, the publisher controls this annotation, so could allow a maintainer to say that their image was published earlier than it had been to bypass Minimum Release Age restrictions.

As long as the registry/datasource provides the release timestamp, Renovate will add support for it.

My package manager has support for minimum release age, how does Renovate work with that? Do I need to set it in both?

We recommend specifying minimum release age in both your Renovate and package manager configuration.

Renovate's concept of minimumReleaseAge is set independently to your package manager's configuration, and is used by Renovate to decide whether to suggest dependency updates. Renovate does not currently have the ability to determine the minimumReleaseAge from your package manager's configuration.

When Renovate performs a dependency update, it may delegate to your package manager to update artifacts, such as the lockfile. In some cases, this can lead to a transitive dependency being introduced, which Renovate is not aware of, and so could lead to a dependency being introduced without Renovate's minimumReleaseAge check being followed.

To protect against this, it's recommended to ensure that your package manager configuration includes the relevant minimum release age checks, too.

There is ongoing work to integrate more closely with package manager checks to make sure that Renovate's minimum release age configuration is specified when calling package managers that support it. If you have a package manager you'd like supported, please raise a Suggest an Idea Discussion.

npm

When minimumReleaseAge is configured, Renovate passes --before=<date> to npm commands during lock file generation. This ensures that npm only resolves package versions that were available before the cooldown threshold, protecting against newly published (and potentially malicious) transitive dependencies.

The --before date is calculated as now - minimumReleaseAge. If a before=<date> or min-release-age=<days> setting already exists in the project's .npmrc, Renovate uses the stricter (older) of the two dates.

If the existing lock file contains packages published after the --before cutoff (for example, from dependencies merged before minimumReleaseAge was configured), npm will fail with an ETARGET error. In this case, Renovate automatically retries without --before and logs a warning. This ensures existing lock files are never broken by the --before flag.

After the next lock file maintenance run (which regenerates the lock file from scratch with --before), subsequent updates will fully enforce the minimumReleaseAge constraint.

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

Warning

Renovate 42 changed 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, we would treat the dependency without a release timestamp as if it has passed the minimumReleaseAge, and will immediately suggest that dependency update. If using Renovate prior you can opt into the more secure behaviour (which is default in Renovate 42) by using minimumReleaseAgeBehaviour=timestamp-required (added in 41.150.0)

Consider that:

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

Renovate will create a PR for the 1 dependency update that has passed the release timestamp, and the others will be marked as "Pending Status Checks" on the Dependency Dashboard. As time goes on, if the 2 updates with a release timestamp are now passed the minimum release age, Renovate will add them to the PR (or create a new one).

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

Renovate will decide whether it will create a branch for a dependency update using internalChecksFilter.

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.

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.

Note

A previous version of the documentation (up until Renovate 42.19.9) recommended configuring prCreation. This is no longer the case.

If no branch is created, Renovate will not raise a PR, regardless of prCreation's settings.

The recommendation is to set internalChecksFilter=strict 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.

Update Type Supports minimumReleaseAge? Notes
major Depends on the Manager, Datasource, and package(s)
minor Depends on the Manager, Datasource, and package(s)
patch Depends on the Manager, Datasource, and package(s)
pin Not yet supported
digest 🟡 Depends on the Manager, Datasource, and package(s). See below for more info.
pinDigest 🟡 Depends on the Manager, Datasource, and package(s). See below for more info.
lockFileMaintenance Not possible, as we delegate to the package manager to perform the required changes to update package(s).
lockfileUpdate
rollback
bump
replacement Not yet supported

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

digest updates

A digest update repoints an existing digest - usually a sha256:... Docker image digest, or the commit a floating tag like v7 points to - at whatever the current version resolves to now.

For instance:

   steps:
-    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

In this case, the version v7 is not updated, but the digest is. When Renovate resolves this update, it looks for the newest version matching the current value (which would be the v7.0.1 version in this case), and then ages the update against the release timestamp for this new version.

Warning

Most registries do not expose when a digest itself was published, so to determine the Minimum Release Age cutoff, we age against the release timestamp of the matched version. The strength of the guarantee depends on what that timestamp means for your datasource.

What this means in practice:

  • On Docker Hub, the release timestamp is the tag's tag_last_pushed field, so re-pushing an existing tag restarts the delay and the digest update is held.
  • Registries which do not return release timestamps (such as GHCR, Quay or ECR) cannot be aged, and the same applies to unversioned tags like latest, where no matching version exists to age against. With the default minimumReleaseAgeBehaviour=timestamp-required, these digest updates are held indefinitely - configure minimumReleaseAgeBehaviour=timestamp-optional to raise them without an age check instead.
  • Datasources whose timestamps reflect when the matched version was first released (such as github-tags for digest-pinned GitHub Actions) cannot detect re-published content. If an existing tag is force-pushed to new commits, the digest update ages against the original release date, so it may pass Minimum Release Age immediately.
    • Note that this is not true when using github-releases, which has metadata to indicate when the GitHub Release was created and published, whereas github-tags uses the committedDate, which is separate from when it was pushed. GitHub no longer provides that metadata in their API.

pinDigest updates

A pinDigest update (enabled via pinDigests) pins an existing versioned tag to its currently resolved digest, without changing the tag itself.

For instance:

-    - uses: actions/checkout@v7
+    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

Unlike a digest update, pinDigest doesn't repoint the reference to a different value - it freezes whatever the current value already resolves to. The reference itself (e.g. the v7 major tag above) is floating though, so what it resolves to right now may not be what currentVersion would resolve to under your configured rangeStrategy (for example, rangeStrategy=bump resolves currentVersion to the oldest matching release). So, like digest, a pinDigest update is aged against the release timestamp of the newest version matching the current value, not whatever currentVersion resolves to.

Unlike digest updates, an unversioned current value (such as latest) is exempt from minimumReleaseAge entirely, rather than being held indefinitely: pinning a reference which already floats to the latest content is strictly safer than leaving it unpinned, so holding it back would only prolong the less-safe, unpinned state.

If the current value does resolve to a version, but that version's datasource doesn't expose a release timestamp (for example GHCR, Quay or ECR), the same caveats as digest updates apply: the update is held indefinitely under the default minimumReleaseAgeBehaviour=timestamp-required, and needs minimumReleaseAgeBehaviour=timestamp-optional configured to raise it without an age check.

Avoiding pinDigest updates not being raised due to frequently-releasing packages

Because a pinDigest update ages against the newest matching version, a reference that releases faster than your configured minimumReleaseAge may never clear the check - leaving it perpetually unpinned, which is less safe than pinning to a slightly-stale release.

In particular, because Renovate raises a pinDigest PR across all applicable dependencies (by grouping them together), this can lead to the whole pinDigest update group being delayed.

Renovate does not fall back to pinning an older, already-aged release instead: doing so would mean the pin no longer reflects what the reference currently, genuinely points to, which is the property pinDigest exists to preserve.

If you want Renovate to raise pinDigest updates sooner than your regular version/digest updates, scope a shorter (or disabled) minimumReleaseAge to it specifically, using packageRules and matchUpdateTypes:

{
  "packageRules": [
    {
      "matchUpdateTypes": ["pinDigest"],
      "minimumReleaseAge": null
    }
  ]
}

This decouples "how fresh must a release be before I trust it enough to upgrade to it" (your regular minimumReleaseAge) from "how long to wait before freezing whatever's already there" (the pinDigest-scoped override) - the same idea as using digest.minimumReleaseAge:

{
  "digest": {
    "minimumReleaseAge": null
  }
}

These are functionally identical options.

What happens to security updates?

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

What happens if a package has multiple updates available?

Note

This is based on the recommended settings above

Renovate waits for the set duration to pass for each separate version.

If Renovate sees that a package has multiple updates available, it will only raise update(s) that are passing the minimumReleaseAge check.

Let us consider a repository with minimumReleaseAge=1 hour, and with the following timeline:

  • 0000: Renovate runs, and sees no updates
  • 0010: Package releases 1.1.0
  • 0030: Renovate runs, and sees 1.1.0 and marks it as pending
  • 0100: Renovate runs, still sees 1.1.0 as pending
  • 0110: Package releases 1.1.1
  • 0130: Renovate runs, and sees 1.1.0 and 1.1.1 releases. As 1.1.0 is now past the minimumReleaseAge, Renovate raises a PR, and marks 1.1.1 as pending
  • 0200: Renovate runs, still sees 1.1.1 as pending
  • 0230: No humans have merged the PR for 1.1.0, so when Renovate runs, it sees 1.1.1 is now past the minimumReleaseAge, so updates the existing PR to bump the version to 1.1.1

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,
    },
  ],
}

Note

As of Renovate 42.19.5, using minimumReleaseAge=0 days is treated the same as minimumReleaseAge=null.

Which datasources support release timestamps?

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.

The table below lists every datasource Renovate supports, and whether it provides a release timestamp.

Datasource Supports release timestamp? Notes
Artifactory 🟠
Maybe1
The release timestamp is determined from the date-like text, next to the version hyperlink tag in the results.
Aws Eks Addon
Aws Machine Image 🟠
Maybe1
The release timestamp is determined from the CreationDate field in the results.
Aws Rds
Azure Bicep Resource
Azure Pipelines Tasks
Azure Tags
Bazel
Bitbucket Server Tags
Bitbucket Tags
Bitrise 🟠
Maybe1
The release timestamp is determined from the published_at field in the results.
Buildpacks Registry 🟠
Maybe1
The release timestamp is determined from the published_at field in the results.
Cdnjs
Clojure 🟠
Maybe1
The release timestamp is determined from the Last-Modified header or the lastModified field in the results.
Conan
Conda
Cpan 🟠
Maybe1
The release timestamp is determined from the date field in the results.
Crate 🟠
Maybe1
The release timestamp is determined from pubtime field from crates.io index if available, or version.created_at field from crates.io API otherwise.
Custom
Dart 🟠
Maybe1
The release timestamp is determined from the published field in the results.
Dart Version
Deb
Deno 🟠
Maybe1
The release timestamp is determined from the uploaded_at field in the results.
Devbox 🟠
Maybe1
Docker 🟠
Maybe1
Only supported on Docker Hub: The release timestamp is determined from the tag_last_pushed field in the results. NOTE: Currently, digests will receive the same release timestamp as the tag_last_pushed, which means that digests may appear newer than they are - see https://github.com/renovatebot/renovate/issues/38659
Dotnet Version 🟠
Maybe1
The release timestamp is determined from the release-date field in the results.
Elm Package 🟠
Maybe1
The release timestamp is determined from the Unix timestamp in the results.
Endoflife Date 🟠
Maybe1
The release timestamp is determined from the releaseDate field in the results.
Flutter Version 🟠
Maybe1
The release timestamp is determined from the release_date field in the results.
Forgejo Releases 🟠
Maybe1
The release timestamp is determined from the published_at field in the results.
Forgejo Tags 🟠
Maybe1
The release timestamp is determined from the created field in the results.
Galaxy 🟠
Maybe1
The release timestamp is determined from the created field in the results.
Galaxy Collection 🟠
Maybe1
The release timestamp is determined from the created_at field in the results.
Git Refs
Git Tags
Gitea Releases 🟠
Maybe1
The release timestamp is determined from the published_at field in the results.
Gitea Tags 🟠
Maybe1
The release timestamp is determined from the created field in the results.
Github Digest 🟠
Maybe1
The release timestamp is determined from the commit date.
Github Release Attachments 🟠
Maybe1
The release timestamp is determined from the releaseTimestamp field in the results.
Github Releases 🟠
Maybe1
The release timestamp is determined from the releaseTimestamp field from the response.
Github Runners
Github Tags 🟠
Maybe1
The get release timestamp is determined from the releaseTimestamp field in the results.
Gitlab Packages 🟠
Maybe1
The release timestamp is determined from the created_at field in the results.
Gitlab Releases 🟠
Maybe1
The release timestamp is determined from the released_at field in the results.
Gitlab Tags 🟠
Maybe1
To get release timestamp we use the created_at field from the response.
Glasskube Packages
Go 🟠
Maybe1
If the release timestamp is not returned from the respective datasoure used to fetch the releases, then Renovate uses the Time field in the results instead.
Golang Version 🟠
Maybe1
The release timestamp is determined from the Date field in the results.
Gradle Version 🟠
Maybe1
The release timestamp is determined from the buildTime field in the results.
Hackage
Helm 🟠
Maybe1
The release timstamp is determined from the created field in the results.
Hermit
Hex 🟠
Maybe1
The release timestamp is determined the inserted_at field in the results.
Hexpm Bob 🟠
Maybe1
The release timestamp is determined from the buildDate field in the results.
Java Version
Jenkins Plugins 🟠
Maybe1
The releaseTimestamp is determined from the releaseTimestamp or buildDate field in the results.
Jsr 🟠
Maybe1
The release timestamp is determined from the createdAt field in the results. For packages without explicit timestamps, defaults to 2025-09-18.
Kubernetes Api
Maven 🟠
Maybe1
The release timestamp is determined from the Last-Modified header or the lastModified field in the results.
Nextcloud
Node Version 🟠
Maybe1
The release timestamp is determined from the date field.
Npm 🟠
Maybe1
The release timestamp is determined from the time field in the results.
Nuget 🟠
Maybe1
For the v2 API, the release timestamp is determined from the Published tag. And, for the v3 API, the release timestamp is determined from the published field in the results.
Orb 🟠
Maybe1
The release timestamp is determined from the createdAt field in the results.
Packagist 🟠
Maybe1
The release timestamp is determined from the time field in the results.
Pod
Puppet Forge 🟠
Maybe1
The release timestamp is determined from the created_at field from the response.
Pypi 🟠
Maybe1
The relase timestamp is determined from the upload_time field in the results. This field is not available when using the simple API.
Python Version
Repology
Rpm
Ruby Version 🟠
Maybe1
The release timestamp is determined from the release-list table in the results.
Rubygems 🟠
Maybe1
The release timestamp is determined from the created_at field in the results.
Rust Version 🟠
Maybe1
The release timestamp is parsed from the release manifest URL.
Sbt Package 🟠
Maybe1
The release timestamp is determined from the Last-Modified header or the lastModified field in the results.
Sbt Plugin
Terraform Module 🟠
Maybe1
For registry.terraform.io and registry.opentofu.org (via api.opentofu.org), release timestamps are available for all versions. For app.terraform.io, only the latest version is annotated, using the published_at field from the extended module endpoint.
Terraform Provider 🟠
Maybe1
The release timestamp is only available for registry.terraform.io (v2 API) and registry.opentofu.org (via api.opentofu.org). Other registries using the Provider Registry Protocol do not provide timestamps.
Typst
Unity3d 🟠
Maybe1
The release timestamp is determined from the releaseDate field in the results.
Unity3d Packages

1 Whether release timestamps are actually available depends on the registry you use - check Which registries support release timestamps?.

Which registries support release timestamps?

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

Datasource Registry URL Supported Notes
crate https://crates.io
rubygems https://rubygems.org
docker https://index.docker.io
docker (not Docker Hub) Issue
docker https://ghcr.io Issue
docker https://quay.io Issue
github-releases https://github.com
terraform-module https://registry.terraform.io
terraform-module https://registry.opentofu.org Queries api.opentofu.org for release timestamps
terraform-provider https://registry.terraform.io
terraform-provider https://registry.opentofu.org Queries api.opentofu.org for release timestamps
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
nuget https://api.nuget.org/v3/index.json
pypi https://pypi.org/pypi/
ruby-version https://www.ruby-lang.org
jsr https://jsr.io For packages without explicit timestamps, defaults to 2025-09-18

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",
      "datasource": null,
      "depName": "renovatebot/.github",
      "packageFile": "renovate.json",
      "registryUrls": []
    },
    {
      "manager": "regex",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "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/"
      ]
    }
  ]
}