Skip to content

Release-age cooldown bypass ignored when package is first resolved as transitive dependency #1187

@andre-motta

Description

@andre-motta

Summary

When a package is declared as a top-level requirement with an == pin (to bypass the release-age cooldown), the bypass is ignored if the same package was already resolved as a transitive dependency earlier in the bootstrap process. This is due to the session-level cache in BootstrapRequirementResolver not including req_type in its cache key.

Root Cause

In src/fromager/bootstrap_requirement_resolver.py, the resolution cache key is (str(req), pre_built):

self._resolved_requirements: dict[
    tuple[str, bool], tuple[tuple[str, Version], ...]
] = {}

The req_type (TOP_LEVEL vs INSTALL) is not part of the cache key. When a package is first encountered as a transitive dependency (RequirementType.INSTALL), the cooldown is enforced and the result is cached. When the same package is later encountered as a top-level requirement (RequirementType.TOP_LEVEL) with an == pin, the cached (cooldown-blocked) result is returned without re-evaluating resolve_package_cooldown().

Reproduction

  1. Package B depends on A>=1.0
  2. Package A==2.0.0 was published within the last X days (within cooldown window)
  3. Declare both A==2.0.0 and B as top-level requirements
  4. If B is processed first, A>=1.0 resolves as transitive → cooldown blocks version 2.0.0 → result cached
  5. When A==2.0.0 is later processed as top-level, cache returns the blocked result → bypass never applies

Expected: Top-level A==2.0.0 should bypass the cooldown regardless of processing order.

Actual: Bypass only works if A==2.0.0 is resolved before any transitive dependency pulls in A.

Suggested Fix

Include req_type in the cache key:

cache_key = (str(req), pre_built, req_type)

This ensures transitive and top-level resolutions are cached separately, preserving the req_type context needed for cooldown bypass decisions.

Workaround

Move the cooldown-bypassing top-level requirement to the very first position in the requirements file so it is resolved before any transitive dependency can pull it in.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions