Skip to content

refactor(resolver): move age filter fallback to resolver with cache server lookup#1163

Merged
mergify[bot] merged 1 commit into
python-wheel-build:mainfrom
rd4398:resolver-fallback-bug
Jun 1, 2026
Merged

refactor(resolver): move age filter fallback to resolver with cache server lookup#1163
mergify[bot] merged 1 commit into
python-wheel-build:mainfrom
rd4398:resolver-fallback-bug

Conversation

@rd4398
Copy link
Copy Markdown
Contributor

@rd4398 rd4398 commented May 19, 2026

Move the two-step fallback logic from _phase_resolve() into
BootstrapRequirementResolver._resolve(). When age filtering removes
all candidates in multi-version mode, the resolver now queries the
remote wheel cache server for the newest available version and returns
only that single version. The resolver warns on zero results but does
not error — _phase_resolve() raises the exception.

Co-Authored-By: Claude claude@anthropic.com

Signed-off-by: Rohan Devasthale rdevasth@redhat.com

Fixes: #1162

@rd4398 rd4398 requested a review from a team as a code owner May 19, 2026 17:46
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Review Change Stack

Warning

Review limit reached

@rd4398, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 20 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c6e68969-0211-4548-9d6f-7f91a5b50cac

📥 Commits

Reviewing files that changed from the base of the PR and between b49421a and 6faec69.

📒 Files selected for processing (7)
  • src/fromager/bootstrap_requirement_resolver.py
  • src/fromager/bootstrapper.py
  • src/fromager/resolver.py
  • tests/test_bootstrap_requirement_resolver.py
  • tests/test_bootstrap_requirement_resolver_multiple.py
  • tests/test_bootstrapper_iterative.py
  • tests/test_cooldown.py
📝 Walkthrough

Walkthrough

find_all_matching_from_provider gained a fallback_on_empty_age_filter parameter to control behavior when max_age_cutoff removes all candidates. BootstrapRequirementResolver now accepts multiple_versions and cache_wheel_server_url, passes fallback_on_empty_age_filter=not multiple_versions during sdist resolution, caches only non-empty results, and will query a remote wheel-cache server via _resolve_from_cache_server when in multi-version mode and initial source resolution is empty. Bootstrapper forwards multiple_versions to the resolver. Tests added for cache-server fallback, age-filter behavior, and iterative empty-resolution handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes fully address issue #1162: the PR implements a two-step fallback that checks the wheel cache first and only re-resolves without age filter as a last resort, preventing resolution of all versions.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: resolver parameter additions, bootstrapper integration, fallback logic, and corresponding test coverage.
Title check ✅ Passed The title accurately reflects the main change: moving age filter fallback logic to the resolver and adding cache server lookup capability.
Description check ✅ Passed The PR description clearly explains the change: moving two-step fallback logic into BootstrapRequirementResolver._resolve() for age filter fallback in multi-version mode, with specific details about behavior and the linked issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mergify mergify Bot added the ci label May 19, 2026
@rd4398
Copy link
Copy Markdown
Contributor Author

rd4398 commented May 19, 2026

I considered two approaches:

  1. Move age filtering to the bootstrapper — Remove age checks from the resolver entirely and filter in the bootstrapper where we have cache access.

  2. Two-step resolution in the bootstrapper (chosen) — Let the resolver signal "all candidates too old" by returning empty, then let the bootstrapper decide what to do based on cache state.

This PR implements approach 2 because

  • The age filter is used in three call sites (resolver.resolve(), bootstrap_requirement_resolver._resolve(), sources.resolve_source()). Moving it out would require threading upload_time metadata through all return paths — today the resolver returns
    list[tuple[str, Version]], not full Candidate objects, so the bootstrapper has no way to inspect upload times without duplicating PyPI metadata lookups.
  • Single-version mode still needs the fallback. The resolver.resolve() and sources.resolve_source() paths return results[0] directly — an empty list there would crash. The fallback_on_empty_age_filter flag (default True) preserves this safety for all existing
    callers while letting multi-version mode opt out.
  • The bootstrapper already has the right context. _phase_resolve() already checks the wheel cache per-version. Adding a "do we have any cached wheel?" check before falling back is a natural extension of that existing pattern, not a new abstraction.

In short: the resolver stays a pure "find candidates" layer, and the bootstrapper — which owns the build/cache decisions — gets to make the smarter choice when no fresh candidates exist.

Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrapper.py Outdated
@dhellmann
Copy link
Copy Markdown
Member

Rohan and I spoke directly about some other changes to this PR to update the contract for the bootstrap resolver to be "tell me the list of versions to build for this rule" and to therefore move some of the new logic from this change out of the phase method and into the bootstrap resolver instead.

We agreed that if the "normal" resolution using the existing steps results in no versions, the resolver should fall back to looking at the remote wheel server (if any) and pick the newest version of the package available on that server that matches the rule, but to ignore the age and to only return 1 version, even in the mode where we are building multiple versions. That will ensure that the bootstrapped re-processes one version of the package to get its transitive dependencies again and keep those up to date, without re-processing all versions of the package when we know they are not new.

We also agreed that the bootstrap resolver should warn if it gets 0 results, but not report an error, and the phase method should be responsible for raising an exception because that is where we know that we will eventually fail the current build because we could not find any versions of a dependency anywhere.

@rd4398 please let me know if I forgot any details about what we said.

@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from b49421a to ce008b0 Compare May 20, 2026 15:31
@rd4398
Copy link
Copy Markdown
Contributor Author

rd4398 commented May 20, 2026

Rohan and I spoke directly about some other changes to this PR to update the contract for the bootstrap resolver to be "tell me the list of versions to build for this rule" and to therefore move some of the new logic from this change out of the phase method and into the bootstrap resolver instead.

We agreed that if the "normal" resolution using the existing steps results in no versions, the resolver should fall back to looking at the remote wheel server (if any) and pick the newest version of the package available on that server that matches the rule, but to ignore the age and to only return 1 version, even in the mode where we are building multiple versions. That will ensure that the bootstrapped re-processes one version of the package to get its transitive dependencies again and keep those up to date, without re-processing all versions of the package when we know they are not new.

We also agreed that the bootstrap resolver should warn if it gets 0 results, but not report an error, and the phase method should be responsible for raising an exception because that is where we know that we will eventually fail the current build because we could not find any versions of a dependency anywhere.

@rd4398 please let me know if I forgot any details about what we said.

@dhellmann yeah that summarizes it! I have pushed changes in latest commit based on our discussion

@rd4398 rd4398 changed the title fix(resolver): use two-step resolution for age filter fallback in multi-version mode refactor(resolver): move age filter fallback to resolver with cache server lookup May 20, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fromager/bootstrap_requirement_resolver.py`:
- Line 131: The code assumes results from _resolve() are non-empty and indexes
results[0], which can raise IndexError when _resolve() returns []; update the
caller (the function using return_all_versions and calling _resolve()) to first
check if results is empty and handle it explicitly—either return [] when
return_all_versions is true or when not, raise a clear resolution exception
(e.g., ResolutionError or ValueError) with a descriptive message—so replace the
direct [results[0]] indexing with an empty-check guard before returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 30c1092f-88ce-44be-af63-edab185d1043

📥 Commits

Reviewing files that changed from the base of the PR and between 699995e and b49421a.

📒 Files selected for processing (4)
  • src/fromager/bootstrap_requirement_resolver.py
  • src/fromager/bootstrapper.py
  • tests/test_bootstrap_requirement_resolver.py
  • tests/test_bootstrapper_iterative.py

Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from 4dad78a to a33acb9 Compare May 20, 2026 15:44
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py
@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from 13a0ff5 to 204b3fe Compare May 26, 2026 13:53
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py
Comment thread src/fromager/bootstrap_requirement_resolver.py
@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from 976d947 to b155fff Compare May 26, 2026 14:20
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from f28f77f to 3b09dab Compare May 26, 2026 15:08
@rd4398
Copy link
Copy Markdown
Contributor Author

rd4398 commented May 27, 2026

@dhellmann I think addressed all the comments. Can you please take a look when time permits?

Also, @LalatenduMohanty would appreciate any feedback that you might have as well

Copy link
Copy Markdown
Member

@dhellmann dhellmann left a comment

Choose a reason for hiding this comment

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

Two small changes about the accuracy of comments and log messages, but otherwise this looks ready.

Comment thread src/fromager/bootstrap_requirement_resolver.py
Comment thread src/fromager/bootstrap_requirement_resolver.py Outdated
@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from e10cf6b to 641d6e4 Compare June 1, 2026 14:34
@rd4398
Copy link
Copy Markdown
Contributor Author

rd4398 commented Jun 1, 2026

@mergify rebase

…erver lookup

Move the two-step fallback logic from `_phase_resolve()` into
`BootstrapRequirementResolver._resolve()`. When age filtering removes
all candidates in multi-version mode, the resolver now queries the
remote wheel cache server for the newest available version and returns
only that single version. The resolver warns on zero results but does
not error — `_phase_resolve()` raises the exception.

Co-Authored-By: Claude <claude@anthropic.com>

Signed-off-by: Rohan Devasthale <rdevasth@redhat.com>
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented Jun 1, 2026

Deprecation notice: This pull request comes from a fork and was rebased using bot_account impersonation. This capability will be removed on July 1, 2026. After this date, the rebase action will no longer be able to rebase fork pull requests with this configuration. Please switch to the update action/command to ensure compatibility going forward.

@rd4398 rd4398 force-pushed the resolver-fallback-bug branch from 641d6e4 to 6faec69 Compare June 1, 2026 15:06
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented Jun 1, 2026

rebase

✅ Branch has been successfully rebased

@mergify mergify Bot merged commit 960728b into python-wheel-build:main Jun 1, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: fromager resolves all versions if age filtering returns 0 candidates

2 participants