diff --git a/NEWS.md b/NEWS.md
index 311e2707d..b1c58551b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,14 +1,18 @@
 # usethis (development version)
 
+* `use_tidy_upkeep_issue()` now records the year it is being run in the
+  `Config/usethis/upkeep` field in DESCRIPTION. If this value exists it is
+  furthermore used to filter the checklist when making the issue.
+
 ## Bug fixes and minor improvements
 
 * `use_package()` now decreases a package minimum version required when
   `min_version` is lower than what is currently specified in the DESCRIPTION
   file (@jplecavalier, #1957).
-  
+
 * `use_data()` now uses serialization version 3 by default. (@laurabrianna, #2044)
 
-* Reverse dependency checks are only suggested if they exist 
+* Reverse dependency checks are only suggested if they exist
   (#1817, @seankross).
 
 # usethis 3.0.0
diff --git a/R/tidyverse.R b/R/tidyverse.R
index 0e101e413..f3a83389d 100644
--- a/R/tidyverse.R
+++ b/R/tidyverse.R
@@ -44,7 +44,9 @@
 #'   tidyverse conventions around GitHub issue label names and colours.
 #'
 #' * `use_tidy_upkeep_issue()` creates an issue containing a checklist of
-#'   actions to bring your package up to current tidyverse standards.
+#'   actions to bring your package up to current tidyverse standards. Also
+#'   records the current date in the `Config/usethis/last-upkeep` field in
+#'   `DESCRIPTION`.
 #'
 #' * `use_tidy_logo()` calls `use_logo()` on the appropriate hex sticker PNG
 #'   file at <https://github.com/rstudio/hex-stickers>.
diff --git a/R/upkeep.R b/R/upkeep.R
index bae8689ea..84f88657a 100644
--- a/R/upkeep.R
+++ b/R/upkeep.R
@@ -16,13 +16,13 @@
 #' @export
 #' @examples
 #' \dontrun{
-#' use_upkeep_issue(2023)
+#' use_upkeep_issue()
 #' }
 use_upkeep_issue <- function(year = NULL) {
   make_upkeep_issue(year = year, tidy = FALSE)
 }
 
-make_upkeep_issue <- function(year, tidy) {
+make_upkeep_issue <- function(year, last_upkeep, tidy) {
   who <- if (tidy) "use_tidy_upkeep_issue()" else "use_upkeep_issue()"
   check_is_package(who)
 
@@ -41,7 +41,7 @@ make_upkeep_issue <- function(year, tidy) {
 
   gh <- gh_tr(tr)
   if (tidy) {
-    checklist <- tidy_upkeep_checklist(year, repo_spec = tr$repo_spec)
+    checklist <- tidy_upkeep_checklist(last_upkeep, repo_spec = tr$repo_spec)
   } else {
     checklist <- upkeep_checklist(tr)
   }
@@ -118,23 +118,25 @@ upkeep_checklist <- function(target_repo = NULL) {
 
 #' @export
 #' @rdname tidyverse
-#' @param year Approximate year when you last touched this package. If `NULL`,
-#'   the default, will give you a full set of actions to perform.
-use_tidy_upkeep_issue <- function(year = NULL) {
-  make_upkeep_issue(year = year, tidy = TRUE)
+#' @param last_upkeep Year of last upkeep. By default, the
+#' `Config/usethis/last-upkeep` field in `DESCRIPTION` is consulted for this, if
+#' it's defined. If there's no information on the last upkeep, the issue will
+#' contain the full checklist.
+use_tidy_upkeep_issue <- function(last_upkeep = last_upkeep_year()) {
+  make_upkeep_issue(year = NULL, last_upkeep = last_upkeep, tidy = TRUE)
+  record_upkeep_date(Sys.Date())
 }
 
 # for mocking
 Sys.Date <- NULL
 
-tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
+tidy_upkeep_checklist <- function(last_upkeep = last_upkeep_year(),
+                                  repo_spec = "OWNER/REPO") {
   desc <- proj_desc()
 
   posit_pkg <- is_posit_pkg()
   posit_person_ok <- is_posit_person_canonical()
 
-  year <- year %||% 2000
-
   bullets <- c(
     "### To begin",
     "",
@@ -142,7 +144,7 @@ tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
     ""
   )
 
-  if (year <= 2000) {
+  if (last_upkeep <= 2000) {
     bullets <- c(
       bullets,
       "### Pre-history",
@@ -157,7 +159,7 @@ tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
       ""
     )
   }
-  if (year <= 2020) {
+  if (last_upkeep <= 2020) {
     bullets <- c(
       bullets,
       "### 2020",
@@ -168,7 +170,7 @@ tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
       ""
     )
   }
-  if (year <= 2021) {
+  if (last_upkeep <= 2021) {
     bullets <- c(
       bullets,
       "### 2021",
@@ -178,7 +180,7 @@ tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
       ""
     )
   }
-  if (year <= 2022) {
+  if (last_upkeep <= 2022) {
     bullets <- c(
       bullets,
       "### 2022",
@@ -191,7 +193,7 @@ tidy_upkeep_checklist <- function(year = NULL, repo_spec = "OWNER/REPO") {
     )
   }
 
-  if (year <= 2023) {
+  if (last_upkeep <= 2023) {
     bullets <- c(
       bullets,
       "### 2023",
@@ -326,3 +328,18 @@ has_old_cran_comments <- function() {
   file_exists(cc) &&
     any(grepl("# test environment", readLines(cc), ignore.case = TRUE))
 }
+
+last_upkeep_date <- function() {
+  as.Date(
+    proj_desc()$get_field("Config/usethis/last-upkeep", "2000-01-01"),
+    format = "%Y-%m-%d"
+  )
+}
+
+last_upkeep_year <- function() {
+  as.integer(format(last_upkeep_date(), "%Y"))
+}
+
+record_upkeep_date <- function(date) {
+  proj_desc_field_update("Config/usethis/last-upkeep", format(date, "%Y-%m-%d"))
+}
diff --git a/man/tidyverse.Rd b/man/tidyverse.Rd
index 277a65452..fe1cd455f 100644
--- a/man/tidyverse.Rd
+++ b/man/tidyverse.Rd
@@ -38,7 +38,7 @@ use_tidy_style(strict = TRUE)
 
 use_tidy_logo(geometry = "240x278", retina = TRUE)
 
-use_tidy_upkeep_issue(year = NULL)
+use_tidy_upkeep_issue(last_upkeep = last_upkeep_year())
 }
 \arguments{
 \item{ref}{Desired Git reference, usually the name of a tag (\code{"v2"}) or
@@ -64,8 +64,10 @@ assumes that you have a hex logo using spec from
 \item{retina}{\code{TRUE}, the default, scales the image on the README,
 assuming that geometry is double the desired size.}
 
-\item{year}{Approximate year when you last touched this package. If \code{NULL},
-the default, will give you a full set of actions to perform.}
+\item{last_upkeep}{Year of last upkeep. By default, the
+\code{Config/usethis/last-upkeep} field in \code{DESCRIPTION} is consulted for this, if
+it's defined. If there's no information on the last upkeep, the issue will
+contain the full checklist.}
 }
 \description{
 These helpers follow tidyverse conventions which are generally a little
@@ -119,7 +121,9 @@ document in a \verb{.github/} subdirectory.
 \item \code{\link[=use_tidy_github_labels]{use_tidy_github_labels()}} calls \code{use_github_labels()} to implement
 tidyverse conventions around GitHub issue label names and colours.
 \item \code{use_tidy_upkeep_issue()} creates an issue containing a checklist of
-actions to bring your package up to current tidyverse standards.
+actions to bring your package up to current tidyverse standards. Also
+records the current date in the \code{Config/usethis/last-upkeep} field in
+\code{DESCRIPTION}.
 \item \code{use_tidy_logo()} calls \code{use_logo()} on the appropriate hex sticker PNG
 file at \url{https://github.com/rstudio/hex-stickers}.
 }
diff --git a/man/use_upkeep_issue.Rd b/man/use_upkeep_issue.Rd
index 704e258bf..a916dafa0 100644
--- a/man/use_upkeep_issue.Rd
+++ b/man/use_upkeep_issue.Rd
@@ -22,6 +22,6 @@ annual package Spring Cleaning.
 }
 \examples{
 \dontrun{
-use_upkeep_issue(2023)
+use_upkeep_issue()
 }
 }
diff --git a/tests/testthat/_snaps/upkeep.md b/tests/testthat/_snaps/upkeep.md
index af2df5d58..46b4fade3 100644
--- a/tests/testthat/_snaps/upkeep.md
+++ b/tests/testthat/_snaps/upkeep.md
@@ -5,7 +5,7 @@
     Output
       ### To begin
       
-      * [ ] `pr_init("upkeep-2023-01")`
+      * [ ] `pr_init("upkeep-2025-01")`
       
       ### Pre-history
       
@@ -58,7 +58,41 @@
       * [ ] `devtools::build_readme()`
       * [ ] [Re-publish released site](https://pkgdown.r-lib.org/dev/articles/how-to-update-released-site.html) if needed
       
-      <sup>Created on 2023-01-01 with `usethis::use_tidy_upkeep_issue()`, using [usethis v1.1.0](https://usethis.r-lib.org)</sup>
+      <sup>Created on 2025-01-01 with `usethis::use_tidy_upkeep_issue()`, using [usethis v1.1.0](https://usethis.r-lib.org)</sup>
+
+# tidy upkeep omits bullets present in last_upkeep
+
+    Code
+      writeLines(tidy_upkeep_checklist())
+    Output
+      ### To begin
+      
+      * [ ] `pr_init("upkeep-2025-01")`
+      
+      ### 2023
+      
+      * [ ] Update email addresses *@rstudio.com -> *@posit.co
+      * [ ] Update copyright holder in DESCRIPTION: `person("Posit Software, PBC", role = c("cph", "fnd"))`
+      * [ ] Run `devtools::document()` to re-generate package-level help topic with DESCRIPTION changes
+      * [ ] `usethis::use_tidy_logo(); pkgdown::build_favicons(overwrite = TRUE)`
+      * [ ] `usethis::use_tidy_coc()`
+      * [ ] Use `pak::pak("OWNER/REPO")` in README
+      * [ ] Consider running `usethis::use_tidy_dependencies()` and/or replace compat files with `use_standalone()`
+      * [ ] Use cli errors or [file an issue](new) if you don't have time to do it now
+      * [ ] `usethis::use_standalone("r-lib/rlang", "types-check")` instead of home grown argument checkers;
+      or [file an issue](new) if you don't have time to do it now
+      * [ ] Add alt-text to pictures, plots, etc; see https://posit.co/blog/knitr-fig-alt/ for examples
+      
+      ### To finish
+      
+      * [ ] `usethis::use_mit_license()`
+      * [ ] `usethis::use_package("R", "Depends", "4.0")`
+      * [ ] `usethis::use_tidy_description()`
+      * [ ] `usethis::use_tidy_github_actions()`
+      * [ ] `devtools::build_readme()`
+      * [ ] [Re-publish released site](https://pkgdown.r-lib.org/dev/articles/how-to-update-released-site.html) if needed
+      
+      <sup>Created on 2025-01-01 with `usethis::use_tidy_upkeep_issue()`, using [usethis v1.1.0](https://usethis.r-lib.org)</sup>
 
 # upkeep bullets don't change accidentally
 
diff --git a/tests/testthat/test-upkeep.R b/tests/testthat/test-upkeep.R
index f0da07051..a53cbe877 100644
--- a/tests/testthat/test-upkeep.R
+++ b/tests/testthat/test-upkeep.R
@@ -1,9 +1,28 @@
 test_that("tidy upkeep bullets don't change accidentally", {
   create_local_package()
   use_mit_license()
+  expect_equal(last_upkeep_year(), 2000L)
 
   local_mocked_bindings(
-    Sys.Date = function() as.Date("2023-01-01"),
+    Sys.Date = function() as.Date("2025-01-01"),
+    usethis_version = function() "1.1.0",
+    author_has_rstudio_email = function() TRUE,
+    is_posit_pkg = function() TRUE,
+    is_posit_person_canonical = function() FALSE
+  )
+
+  expect_snapshot(writeLines(tidy_upkeep_checklist()))
+})
+
+test_that("tidy upkeep omits bullets present in last_upkeep", {
+  create_local_package()
+  use_mit_license()
+  expect_equal(last_upkeep_year(), 2000L)
+  record_upkeep_date(as.Date("2023-04-04"))
+  expect_equal(last_upkeep_year(), 2023L)
+
+  local_mocked_bindings(
+    Sys.Date = function() as.Date("2025-01-01"),
     usethis_version = function() "1.1.0",
     author_has_rstudio_email = function() TRUE,
     is_posit_pkg = function() TRUE,