Skip to content

fix(bootstrap): warm the resolver cache before adding top-level nodes - #1336

Open
andre-motta wants to merge 1 commit into
python-wheel-build:mainfrom
andre-motta:fix-toplevel-stale-node
Open

andre-motta wants to merge 1 commit into
python-wheel-build:mainfrom
andre-motta:fix-toplevel-stale-node

Conversation

@andre-motta

@andre-motta andre-motta commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #1334.

Top-level requirements were resolved and written to the dependency graph one rule at a time. A later rule for the same package (an exact == pin that bypasses the release-age cooldown) widens the resolver's shared per-package cache, so the Resolve phase settled on a different version than the one already in the graph, and Start skips graph insertion for top-level requirements. The stale node stayed in graph.json with edges: [] and build-parallel scheduled it in round 1.

Fix: resolve every top-level rule once before any of them is added to the graph, so the recorded version matches what the Resolve phase uses. The extra pass only hits the resolver cache on the second lookup, and errors are still reported by the existing code path.

Tests: regression for the unpinned-then-pinned case (fails on main with {'pkg==0.39.0', 'pkg==0.39.1'} == {'pkg==0.39.1'}) and a multiple-versions guard. pytest: 952 passed, 3 skipped. ruff, mypy clean.

🤖 Generated with Claude Code

@andre-motta
andre-motta requested a review from a team as a code owner September 18, 2026 13:45
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e4f80e19-d41c-4679-a2e4-c1d72de6ed44

📥 Commits

Reviewing files that changed from the base of the PR and between 0883ce2 and 64ea41d.

📒 Files selected for processing (2)
  • src/fromager/bootstrapper/_bootstrapper.py
  • tests/test_bootstrapper.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The bootstrapper now pre-warms resolver results for all top-level requirements before graph insertion. It passes the top-level request type and multiple-version setting to resolve_versions, and defers any warm-up exceptions to later resolution. New tests verify that stale nodes are excluded and that graph nodes match started versions, including ROOT edges and multiple-versions mode.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~15 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 64ea4

The top-level resolution fix maintains consistent graph state regardless of the pinned and unpinned requirement order. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: warming the resolver cache during bootstrap to fix stale top-level dependency nodes.
Description check ✅ Passed The description directly explains the stale-node bug, the cache warm-up fix, linked issue, regression tests, and validation results.
Linked Issues check ✅ Passed Issue #1334 requires consistent top-level graph nodes after shared-cache resolution. Bootstrapper.bootstrap() now resolves every top-level requirement in a warm-up pass before `resolve_and_add_top
Out of Scope Changes check ✅ Passed The source change updates top-level resolver-cache ordering. The added tests verify graph nodes, ROOT edges, and started versions for the reported scenarios. These changes directly support Issue #1334

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.

@mergify mergify Bot added the ci label Sep 18, 2026
@andre-motta
andre-motta force-pushed the fix-toplevel-stale-node branch from c2b5d6a to 66e7e76 Compare September 18, 2026 13:47
Top-level requirements were resolved and recorded in the dependency graph
one rule at a time. A later rule for the same package, such as an exact
pin that bypasses the release-age cooldown, widens the resolver's shared
per-package cache, so the Resolve phase then returned a different version
than the one already written to the graph. Because Start skips graph
insertion for top-level requirements, the stale node stayed in graph.json
with no build-system edges and build-parallel scheduled it in round 1
before its build backend was available.

Resolve every top-level rule once before any of them is added to the
graph, so the recorded version matches what the Resolve phase uses.

Closes python-wheel-build#1334

Co-Authored-By: Claude Opus <noreply@anthropic.com>
Signed-off-by: Andre Lustosa <alustosa@redhat.com>
@andre-motta
andre-motta force-pushed the fix-toplevel-stale-node branch from 66e7e76 to 64ea41d Compare September 18, 2026 13:49
@andre-motta andre-motta changed the title fix(bootstrap): derive top-level graph nodes from the final resolution fix(bootstrap): warm the resolver cache before adding top-level nodes Sep 18, 2026
@andre-motta

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rd4398 rd4398 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks mostly good. I have left a comment about exception handling.

Thank you for the fix!

# exact pin that bypasses the release-age cooldown) changes what an
# earlier rule resolves to. Errors are reported by
# _resolve_and_add_top_level below.
for req in requirements:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The warm-up loop resolves every top-level requirement with req_type=TOP_LEVEL, which triggers cooldown bypass logic for == pins (via resolve_package_cooldown). However, the warm-up swallows all
exceptions with a bare except Exception: pass, including ValueError from PEP 508 direct-reference URLs (req.url). While _resolve_and_add_top_level handles these errors properly on the second call, the silent swallow means the warm-up could mask unexpected errors during debugging.

More importantly, the warm-up currently doesn't set the requirement_ctxvar context, so any
log messages emitted during warm-up resolution (e.g. 'cooldown bypassed as the top-level requirement uses == pin') will lack the package name context that the main loop provides via requirement_ctxvar.set(req). This inconsistency means duplicate log lines: once without context during warm-up, once with context during _resolve_and_add_top_level

Suggestion:

Set requirement_ctxvar around each warm-up call to maintain consistent log context, and
consider logging a debug message when catching the exception so silent failures are traceable:

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.

Top-level pre-resolution leaves an unprocessed, edge-less node in graph.json when a second rule for the same package bypasses cooldown

2 participants