Skip to content

Filter same-version rebuilds from pkg_outdated - #1351

Merged
juliasilge merged 5 commits into
mainfrom
bugfix/pkg-outdated-version-filter
Aug 4, 2026
Merged

Filter same-version rebuilds from pkg_outdated#1351
juliasilge merged 5 commits into
mainfrom
bugfix/pkg-outdated-version-filter

Conversation

@juliasilge

Copy link
Copy Markdown
Member

This addresses part of posit-dev/positron#14294, where the packages pane surfaces a large number of packages as having updates available when in fact they don't.

.ps.rpc.pkg_outdated() was returning everything from utils::old.packages(). That function flags a package as outdated when the repository version is strictly newer or when the version is identical but the repository copy has a newer build or publication date (see the needs.install closure inside utils:::old.packages). Right after a new R minor is installed, CRAN's freshly rebuilt binaries carry newer Built dates than the user's carried-over packages, so hundreds of same-version packages get flagged even though no newer version exists.

The pane's update indicator means "a newer version is available," so this filters old.packages() down to packages whose repository version is strictly greater than the installed version, dropping same-version rebuilds.

Changes

  • Split the filtering and formatting out of .ps.rpc.pkg_outdated() into a pure pkg_outdated_result() helper so it can be tested without querying live repositories.
  • Keep only packages where ReposVer > Installed (compared with package_version(), so 10.0.0 correctly beats 9.0.0).

Testing

Added integration tests in crates/ark/tests/integration/packages_pane.rs driving pkg_outdated_result() with fabricated old.packages() matrices: a genuine upgrade is kept, a same-version rebuild is dropped, a mixed list keeps only real upgrades in order, versions compare numerically rather than as strings, and NULL / all-rebuild inputs return an empty list.

Also verified manually that old.packages() really does flag a same-version rebuild (via injected instPkgs/available matrices with a newer Built date) and that the filter drops it while keeping a genuine upgrade.

Positron Release Notes

New Features

  • N/A

Bug Fixes

@juliasilge
juliasilge requested a review from jennybc July 16, 2026 18:55
juliasilge added a commit to posit-dev/positron that referenced this pull request Jul 17, 2026
… is clicked (#14918)

Addresses to #14294 (together with
posit-dev/ark#1351)

The Packages pane persists outdated-package state to an on-disk cache so
update indicators can render immediately on a warm start. The toolbar
Refresh button reused that cache so that within the freshness window
(default 24h) it re-rendered the cached indicators instead of
recomputing, so an explicit Refresh could keep showing stale "outdated"
flags that no longer matched the environment. Only the less-discoverable
"Refresh Metadata" command forced a live recompute.

This makes the user-initiated Refresh authoritative. It now always
recomputes outdated state live for every package, bypassing the
freshness window, while automatic warm-start refreshes (on session
attach) keep using the fast cached path. A forced recompute overwrites
stale `outdated: true` entries with the current result, so clicking
Refresh reliably clears indicators that no longer apply.

This is the Positron half of #14294. The complementary Ark change
(posit-dev/ark#1351) stops `pkg_outdated` from flagging same-version
packages that were merely rebuilt under a new R minor, which was the
original trigger for the inflated counts.

### Release Notes

#### New Features
- N/A

#### Bug Fixes
- Packages pane: the Refresh button now recomputes outdated packages
live instead of showing stale cached indicators (#14294)

### Validation Steps

@:packages-pane

The bug only surfaces when the cache disagrees with reality while
installed versions stay fixed, so the reliable way to exercise it is to
change the configured repositories (the cache is keyed on the
interpreter, not the repos) and confirm the pane tracks a live
`old.packages()` after Refresh. It's pretty fiddly!

1. Start an R session and point at a snapshot recent enough that most
installed packages are current:

   ```r
options(repos = c(CRAN = "https://packagemanager.posit.co/cran/latest"))
   ```

2. Open the Packages pane, click the Refresh button, and note the
outdated count. Confirm it matches ground truth from the console:

   ```r
   nrow(old.packages())
   ```

3. Without reinstalling anything, point at an older dated snapshot so
some installed versions are now behind:

   ```r
options(repos = c(CRAN =
"https://packagemanager.posit.co/cran/2024-06-01"))
   ```

4. Click Refresh again. The pane's outdated indicators should update to
match the new `nrow(old.packages())`, even though only the repos
changed. Before this fix, within the freshness window the pane would
keep showing the step-2 result from the cache.

The Python equivalent is the same shape: install a package pinned below
its latest version (`pip install "requests<2.31"`), Refresh so the pane
caches it as outdated, upgrade it in the console (`pip install -U
requests`), and confirm Refresh clears the indicator to match `pip list
--outdated`.
@juliasilge
juliasilge requested a review from bricestacey August 3, 2026 15:35
@juliasilge

Copy link
Copy Markdown
Member Author

@bricestacey, I believe Jenny is OOO now and we'd like to get this fix in before we branch for 2026.08. Can you take a look and review, at least based on what your understanding is? Thanks!

@juliasilge
juliasilge requested a review from lionel- August 4, 2026 14:29
@juliasilge

Copy link
Copy Markdown
Member Author

@lionel-, I am having a bit of trouble getting this PR reviewed. 😅 We do want to get this fix in for this milestone, so could you take a look at it?

We believe there may be further work we can do here for packages with compiled code that actually should be marked outdated in some situations, but this is a first step to improve what people are seeing.

@lionel- lionel- 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.

LG although a real integration test would be great

Comment on lines +198 to +210
# Turn an `old.packages()` matrix into the pane's outdated-package list. Split
# out from the RPC so the filtering and formatting can be tested without
# querying live repositories.
#
# We keep only packages whose repository version is strictly greater than the
# installed version. old.packages() also flags packages whose repository copy
# has the *same* version but a newer build or publication date (see the
# `needs.install` closure inside utils::old.packages). Right after a new R
# minor is installed, CRAN's freshly rebuilt binaries carry newer Built dates
# than the user's carried-over packages, so hundreds of same-version packages
# get flagged even though no newer version exists. The pane's update indicator
# means "a newer version is available", so a same-version rebuild is not an
# update we should surface.

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.

Does this simpler comment (to move below near the check) retain all useful information?

# Since R 4.6, `old.packages()` also reports equal-version packages with newer
# `Built` or `Published` timestamps. The pane promises a newer package version,
# so exclude those entries.

@@ -0,0 +1,107 @@
//
// packages_pane.rs

@lionel- lionel- Aug 4, 2026

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.

I'd love an integration test that exercises the format produced by the R version on CI.

Claude thinks this is feasible. If you have time, could you take a look?

A real integration test is cheap

No network, no `R CMD INSTALL`, no compilation. `installed.packages()` only needs `<lib>/<pkg>/Meta/package.rds` holding a list with `$DESCRIPTION` and `$Built$R` (`utils:::.readPkgDesc`), and `available.packages()` only needs a DCF `PACKAGES` file under `<repo>/src/contrib`. Both are a few `saveRDS()`/`cat()` calls, and the run is instant — the whole thing is much cheaper than the current six r_task processes.

```r
lib <- tempfile("lib"); repo <- tempfile("repo")
dir.create(file.path(repo, "src", "contrib"), recursive = TRUE)

install_fake <- function(pkg, version, built) {
    dir.create(file.path(lib, pkg, "Meta"), recursive = TRUE)
    saveRDS(
        list(
            DESCRIPTION = c(Package = pkg, Version = version, Built = built),
            Built = list(R = package_version(sub("^R ([0-9.]+).*", "\\1", built)))
        ),
        file.path(lib, pkg, "Meta", "package.rds")
    )
}

install_fake("rebuild", "1.5.0", "R 4.5.2; ; 2026-01-01 00:00:00 UTC; unix")
install_fake("upgrade", "1.0.0", "R 4.6.0; ; 2026-05-01 00:00:00 UTC; unix")
# PACKAGES: rebuild 1.5.0 with a newer `Built:` date, upgrade 2.0.0

options(
    repos = c(CRAN = paste0("file:///", normalizePath(repo, winslash = "/"))),
    pkgType = "source",
    # `Built` is only standard for binary repos, so ask for it explicitly and
    # the fixture stays portable instead of faking a mac.binary layout.
    available_packages_fields = "Built"
)
.libPaths(c(lib, .libPaths()))
.ps.rpc.pkg_outdated()
```

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done! Thanks for that idea, which works pretty much as written, with just some adjustments for older R.

The one thing worth a look is the version gating. On R < 4.6 old.packages() never flags the same-version rebuild, so "the rebuild is absent from the result" would pass vacuously on most of our matrix. The tests now assert that old.packages() flags it exactly when getRversion() >= "4.6.0", so the fixture fails loudly if it ever stops reproducing the 4.6 behavior instead of going quiet.

Verified passing against R 4.2.1, 4.4.2, and 4.6.0 locally, and confirmed both tests fail if I relax the filter to >=.

@juliasilge
juliasilge merged commit 2a70d3e into main Aug 4, 2026
17 checks passed
@juliasilge
juliasilge deleted the bugfix/pkg-outdated-version-filter branch August 4, 2026 17:39
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants