Repository: rust-lang/triagebot
Pull request #2005: Fix `[behind-upstream]` when dealing with subtrees
Author: login: Urgau
Created: 2025-05-22T16:47:27Z | Merged: 2025-05-23T10:17:59Z

This PR fixes our logic *flawed* logic for the `[behind-upstream]` handler, which just looked at the parent commit of the first commit of the branch, which doesn't work in the presence of subtree.

Instead we are using [`git merge-base`](https://git-scm.com/docs/git-merge-base), provided by GitHub compare API ([example from the issue](https://api.github.com/repos/rust-lang/rust/compare/6eef33bb399cabfab16aa4e0825895f5f32f4e26...3f0c39de36efd6ed4ee3e7a6979a0d71a04ffcce)). This is more robust at it gives us the first common ancestors between the master branch and the pull branch.

(a side bonus is that `[behind-upstream]` handler is now much less complex)

Fixes #2003

Linked issue #2003: Incorrect warning about outdated upstream commit
rustbot [claims](https://github.com/rust-lang/rust/pull/141383#issuecomment-2900316108) that my PR is based on a very old upstream commit. That's not correct though, it is based on https://github.com/rust-lang/rust/commit/2b96ddca1272960623e41829439df8dae82d20af which isn't that old.

Probably the bot gets confused by this being a subtree sync that contains merge commits.
Linked issue comments: user: login: RalfJung
created_at: 2025-05-22T10:24:08Z
body: This was implemented in https://github.com/rust-lang/triagebot/pull/1942, Cc @xizheyin @Kobzol @Urgau 
author_association: MEMBER
user: login: Urgau
created_at: 2025-05-22T11:30:10Z
body: Our current logic is too naïve, we only look at the lastest commit date, which is unrealible for subtree.

I had a quick look and I think we would be better off using the "merge base" commit. Which we can get from GitHub compare API. (I tested it on your PR and it seems to give the right commit).
author_association: MEMBER
user: login: Kobzol
created_at: 2025-05-22T12:24:19Z
body: Once again, a nice simple heuristic broken by subtrees xD
author_association: MEMBER
user: login: xizheyin
created_at: 2025-05-22T12:44:41Z
body: Haha, was there a similar situation before?
author_association: MEMBER
user: login: xizheyin
created_at: 2025-05-22T13:08:13Z
body: I didn't figure out why it was giving false warnings. The head commit of https://github.com/rust-lang/rust/pull/141383#issuecomment-2900316108 should be [here](https://api.github.com/repos/RalfJung/rust/commits/2eb935d25c4d549477cad4990bce7897fe01dc19), and according to our implementation, its first parent commit is [here](https://api.github.com/repos/RalfJung/rust/commits/268a3694244e53c93f8dde8f58f19caf54734360) and the commit date is `2025-05-21T19:48:09Z`.
author_association: MEMBER
user: login: Urgau
created_at: 2025-05-22T16:09:44Z
body: @xizheyin The HEAD commit is actually https://github.com/rust-lang/rust/pull/141383/commits/3f0c39de36efd6ed4ee3e7a6979a0d71a04ffcce, but it's not the one we are looking for, we are looking at oldest/first one, which is https://github.com/rust-lang/rust/pull/141383/commits/f9a75a00b4df355f439cc91b0a660ac508ac9d46, and it's parent has a commit date of `2025-03-31T` (52 days).
author_association: MEMBER
user: login: Urgau
created_at: 2025-05-22T16:47:41Z
body: Opened https://github.com/rust-lang/triagebot/pull/2005 to fix the issue
author_association: MEMBER
user: login: xizheyin
created_at: 2025-05-22T20:00:30Z
body: Oh, I remembered wrong. We were looking for `commits.first()`. Indeed, the latest method will be more robust.
author_association: MEMBER

Diff:
diff --git a/src/github.rs b/src/github.rs
index 8c5438d7f..48e4e235a 100644
--- a/src/github.rs
+++ b/src/github.rs
@@ -320,6 +320,18 @@ pub struct FileDiff {
     pub diff: String,
 }
 
+/// The return from GitHub compare API
+#[derive(Debug, serde::Deserialize)]
+pub struct GithubCompare {
+    /// The base commit of the PR
+    pub base_commit: GithubCommit,
+    /// The merge base commit
+    ///
+    /// See <https://git-scm.com/docs/git-merge-base> for more details
+    pub merge_base_commit: GithubCommit,
+    // FIXME: Also retrieve and use the files list (see our diff function)
+}
+
 impl PullRequestDetails {
     pub fn new() -> PullRequestDetails {
         PullRequestDetails {
@@ -994,6 +1006,23 @@ impl Issue {
         Ok(Some(diff))
     }
 
+    /// Returns the comparison of this event.
+    ///
+    /// Returns `None` if the issue is not a PR.
+    pub async fn compare(&self, client: &GithubClient) -> anyhow::Result<Option<GithubCompare>> {
+        let (before, after) = if let (Some(base), Some(head)) = (&self.base, &self.head) {
+            (&base.sha, &head.sha)
+        } else {
+            return Ok(None);
+        };
+
+        let req = client.get(&format!(
+            "{}/compare/{before}...{after}",
+            self.repository().url(client)
+        ));
+        Ok(Some(client.json(req).await?))
+    }
+
     /// Returns the commits from this pull request (no commits are returned if this `Issue` is not
     /// a pull request).
     pub async fn commits(&self, client: &GithubClient) -> anyhow::Result<Vec<GithubCommit>> {
diff --git a/src/handlers/check_commits.rs b/src/handlers/check_commits.rs
index c5703e9e2..526cbcf0e 100644
--- a/src/handlers/check_commits.rs
+++ b/src/handlers/check_commits.rs
@@ -58,6 +58,12 @@ pub(super) async fn handle(ctx: &Context, event: &Event, config: &Config) -> any
             event.issue.number
         )
     };
+    let Some(compare) = event.issue.compare(&ctx.github).await? else {
+        bail!(
+            "expected issue {} to be a PR, but the compare could not be determined",
+            event.issue.number
+        )
+    };
     let commits = event.issue.commits(&ctx.github).await?;
 
     let mut warnings = Vec::new();
@@ -101,7 +107,7 @@ pub(super) async fn handle(ctx: &Context, event: &Event, config: &Config) -> any
             .unwrap_or(behind_upstream::DEFAULT_DAYS_THRESHOLD);
 
         if let Some(warning) =
-            behind_upstream::behind_upstream(age_threshold, event, &ctx.github, &commits).await
+            behind_upstream::behind_upstream(age_threshold, event, &compare).await
         {
             warnings.push(warning);
         }
diff --git a/src/handlers/check_commits/behind_upstream.rs b/src/handlers/check_commits/behind_upstream.rs
index 34dc28dc2..b9dec019d 100644
--- a/src/handlers/check_commits/behind_upstream.rs
+++ b/src/handlers/check_commits/behind_upstream.rs
@@ -1,4 +1,4 @@
-use crate::github::{GithubClient, GithubCommit, IssuesEvent, Repository};
+use crate::github::{GithubCompare, IssuesEvent};
 use tracing as log;
 
 /// Default threshold for parent commit age in days to trigger a warning
@@ -8,95 +8,32 @@ pub(super) const DEFAULT_DAYS_THRESHOLD: usize = 7;
 pub(super) async fn behind_upstream(
     age_threshold: usize,
     event: &IssuesEvent,
-    client: &GithubClient,
-    commits: &Vec<GithubCommit>,
+    compare: &GithubCompare,
 ) -> Option<String> {
     log::debug!("Checking if PR #{} is behind upstream", event.issue.number);
 
-    let Some(head_commit) = commits.first() else {
-        return None;
-    };
+    // Compute the number of days old the merge base commit is
+    let commit_date = compare.merge_base_commit.commit.author.date;
+    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
+    let days_old = (now - commit_date).num_days() as usize;
 
     // First try the parent commit age check as it's more accurate
-    match is_parent_commit_too_old(head_commit, &event.repository, client, age_threshold).await {
-        Ok(Some(days_old)) => {
-            log::info!(
-                "PR #{} has a parent commit that is {} days old",
-                event.issue.number,
-                days_old
-            );
-
-            return Some(format!(
-                r"This PR is based on an upstream commit that is {} days old.
-
-*It's recommended to update your branch according to the [rustc_dev_guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
-                days_old
-            ));
-        }
-        Ok(None) => {
-            // Parent commit is not too old, log and do nothing
-            log::debug!("PR #{} parent commit is not too old", event.issue.number);
-        }
-        Err(e) => {
-            // Error checking parent commit age, log and do nothing
-            log::error!(
-                "Error checking parent commit age for PR #{}: {}",
-                event.issue.number,
-                e
-            );
-        }
-    }
-
-    None
-}
-
-/// Checks if the PR's parent commit is too old.
-///
-/// This determines if a PR needs updating by examining the first parent of the PR's head commit,
-/// which typically represents the base branch commit that the PR is based on.
-///
-/// If this parent commit is older than the specified threshold, it suggests the PR
-/// should be updated/rebased to a more recent version of the base branch.
-///
-/// Returns:
-/// - Ok(Some(days_old)) - If parent commit is older than the threshold
-/// - Ok(None)
-///     - If there is no parent commit
-///     - If parent is within threshold
-/// - Err(...) - If an error occurred during processing
-pub(super) async fn is_parent_commit_too_old(
-    commit: &GithubCommit,
-    repo: &Repository,
-    client: &GithubClient,
-    max_days_old: usize,
-) -> anyhow::Result<Option<usize>> {
-    // Get the first parent (it should be from the base branch)
-    let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else {
-        return Ok(None);
-    };
-
-    let days_old = commit_days_old(&parent_sha, repo, client).await?;
-
-    if days_old > max_days_old {
-        Ok(Some(days_old))
+    if days_old > age_threshold {
+        log::info!(
+            "PR #{} has a parent commit that is {} days old",
+            event.issue.number,
+            days_old
+        );
+
+        Some(format!(
+            r"This PR is based on an upstream commit that is {} days old.
+
+*It's recommended to update your branch according to the [rustc-dev-guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
+            days_old
+        ))
     } else {
-        Ok(None)
+        // Parent commit is not too old, log and do nothing
+        log::debug!("PR #{} parent commit is not too old", event.issue.number);
+        None
     }
 }
-
-/// Returns the number of days old the commit is
-pub(super) async fn commit_days_old(
-    sha: &str,
-    repo: &Repository,
-    client: &GithubClient,
-) -> anyhow::Result<usize> {
-    // Get the commit details to check its date
-    let commit: GithubCommit = repo.github_commit(client, &sha).await?;
-
-    // compute the number of days old the commit is
-    let commit_date = commit.commit.author.date;
-    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
-    let days_old = (now - commit_date).num_days() as usize;
-
-    Ok(days_old)
-}


Reviews:
user: login: xizheyin
body: Quite a beautiful solution
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T20:11:15Z
user: login: Urgau
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T21:41:04Z
user: login: Kobzol
body: Shame that GitHub doesn't include this directly in the main PR message. I did a few experiments and tried to think of simpler heuristics, but the merge base seems like it should be robust against various edge cases and issues. Left one nit, but with or without dealing with it feel free to merge.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-23T06:41:09Z
user: login: xizheyin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T07:53:09Z

Review comments:
diff_hunk: @@ -8,95 +8,32 @@ pub(super) const DEFAULT_DAYS_THRESHOLD: usize = 7;
 pub(super) async fn behind_upstream(
     age_threshold: usize,
     event: &IssuesEvent,
-    client: &GithubClient,
-    commits: &Vec<GithubCommit>,
+    compare: &GithubCompare,
 ) -> Option<String> {
     log::debug!("Checking if PR #{} is behind upstream", event.issue.number);
 
-    let Some(head_commit) = commits.first() else {
-        return None;
-    };
+    // Compute the number of days old the merge base commit is
+    let commit_date = compare.merge_base_commit.commit.author.date;
+    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
+    let days_old = (now - commit_date).num_days() as usize;
 
     // First try the parent commit age check as it's more accurate
-    match is_parent_commit_too_old(head_commit, &event.repository, client, age_threshold).await {
-        Ok(Some(days_old)) => {
-            log::info!(
-                "PR #{} has a parent commit that is {} days old",
-                event.issue.number,
-                days_old
-            );
-
-            return Some(format!(
-                r"This PR is based on an upstream commit that is {} days old.
-
-*It's recommended to update your branch according to the [rustc_dev_guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
-                days_old
-            ));
-        }
-        Ok(None) => {
-            // Parent commit is not too old, log and do nothing
-            log::debug!("PR #{} parent commit is not too old", event.issue.number);
-        }
-        Err(e) => {
-            // Error checking parent commit age, log and do nothing
-            log::error!(
-                "Error checking parent commit age for PR #{}: {}",
-                event.issue.number,
-                e
-            );
-        }
-    }
-
-    None
-}
-
-/// Checks if the PR's parent commit is too old.
-///
-/// This determines if a PR needs updating by examining the first parent of the PR's head commit,
-/// which typically represents the base branch commit that the PR is based on.
-///
-/// If this parent commit is older than the specified threshold, it suggests the PR
-/// should be updated/rebased to a more recent version of the base branch.
-///
-/// Returns:
-/// - Ok(Some(days_old)) - If parent commit is older than the threshold
-/// - Ok(None)
-///     - If there is no parent commit
-///     - If parent is within threshold
-/// - Err(...) - If an error occurred during processing
-pub(super) async fn is_parent_commit_too_old(
-    commit: &GithubCommit,
-    repo: &Repository,
-    client: &GithubClient,
-    max_days_old: usize,
-) -> anyhow::Result<Option<usize>> {
-    // Get the first parent (it should be from the base branch)
-    let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else {
-        return Ok(None);
-    };
-
-    let days_old = commit_days_old(&parent_sha, repo, client).await?;
-
-    if days_old > max_days_old {
-        Ok(Some(days_old))
+    if days_old > age_threshold {
+        log::info!(
+            "PR #{} has a parent commit that is {} days old",
+            event.issue.number,
+            days_old
+        );
+
+        Some(format!(
+            r"This PR is based on an upstream commit that is {} days old.
path: src/handlers/check_commits/behind_upstream.rs
user: login: xizheyin
body: Is it necessary to list the link of base commit to give more adequate hints?
created_at: 2025-05-22T20:05:34Z
author_association: MEMBER
diff_hunk: @@ -8,95 +8,32 @@ pub(super) const DEFAULT_DAYS_THRESHOLD: usize = 7;
 pub(super) async fn behind_upstream(
     age_threshold: usize,
     event: &IssuesEvent,
-    client: &GithubClient,
-    commits: &Vec<GithubCommit>,
+    compare: &GithubCompare,
 ) -> Option<String> {
     log::debug!("Checking if PR #{} is behind upstream", event.issue.number);
 
-    let Some(head_commit) = commits.first() else {
-        return None;
-    };
+    // Compute the number of days old the merge base commit is
+    let commit_date = compare.merge_base_commit.commit.author.date;
+    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
+    let days_old = (now - commit_date).num_days() as usize;
 
     // First try the parent commit age check as it's more accurate
-    match is_parent_commit_too_old(head_commit, &event.repository, client, age_threshold).await {
-        Ok(Some(days_old)) => {
-            log::info!(
-                "PR #{} has a parent commit that is {} days old",
-                event.issue.number,
-                days_old
-            );
-
-            return Some(format!(
-                r"This PR is based on an upstream commit that is {} days old.
-
-*It's recommended to update your branch according to the [rustc_dev_guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
-                days_old
-            ));
-        }
-        Ok(None) => {
-            // Parent commit is not too old, log and do nothing
-            log::debug!("PR #{} parent commit is not too old", event.issue.number);
-        }
-        Err(e) => {
-            // Error checking parent commit age, log and do nothing
-            log::error!(
-                "Error checking parent commit age for PR #{}: {}",
-                event.issue.number,
-                e
-            );
-        }
-    }
-
-    None
-}
-
-/// Checks if the PR's parent commit is too old.
-///
-/// This determines if a PR needs updating by examining the first parent of the PR's head commit,
-/// which typically represents the base branch commit that the PR is based on.
-///
-/// If this parent commit is older than the specified threshold, it suggests the PR
-/// should be updated/rebased to a more recent version of the base branch.
-///
-/// Returns:
-/// - Ok(Some(days_old)) - If parent commit is older than the threshold
-/// - Ok(None)
-///     - If there is no parent commit
-///     - If parent is within threshold
-/// - Err(...) - If an error occurred during processing
-pub(super) async fn is_parent_commit_too_old(
-    commit: &GithubCommit,
-    repo: &Repository,
-    client: &GithubClient,
-    max_days_old: usize,
-) -> anyhow::Result<Option<usize>> {
-    // Get the first parent (it should be from the base branch)
-    let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else {
-        return Ok(None);
-    };
-
-    let days_old = commit_days_old(&parent_sha, repo, client).await?;
-
-    if days_old > max_days_old {
-        Ok(Some(days_old))
+    if days_old > age_threshold {
+        log::info!(
+            "PR #{} has a parent commit that is {} days old",
+            event.issue.number,
+            days_old
+        );
+
+        Some(format!(
+            r"This PR is based on an upstream commit that is {} days old.
path: src/handlers/check_commits/behind_upstream.rs
user: login: Urgau
body: We could give similar instructions as when we detect merge commits. https://github.com/rust-lang/triagebot/blob/14fefb68c4831b01f0294d6c03c0ef5e09c35cc7/src/handlers/check_commits/no_merges.rs#L70-L75
created_at: 2025-05-22T21:41:04Z
author_association: MEMBER
diff_hunk: @@ -8,95 +8,32 @@ pub(super) const DEFAULT_DAYS_THRESHOLD: usize = 7;
 pub(super) async fn behind_upstream(
     age_threshold: usize,
     event: &IssuesEvent,
-    client: &GithubClient,
-    commits: &Vec<GithubCommit>,
+    compare: &GithubCompare,
 ) -> Option<String> {
     log::debug!("Checking if PR #{} is behind upstream", event.issue.number);
 
-    let Some(head_commit) = commits.first() else {
-        return None;
-    };
+    // Compute the number of days old the merge base commit is
+    let commit_date = compare.merge_base_commit.commit.author.date;
+    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
+    let days_old = (now - commit_date).num_days() as usize;
 
     // First try the parent commit age check as it's more accurate
-    match is_parent_commit_too_old(head_commit, &event.repository, client, age_threshold).await {
-        Ok(Some(days_old)) => {
-            log::info!(
-                "PR #{} has a parent commit that is {} days old",
-                event.issue.number,
-                days_old
-            );
-
-            return Some(format!(
-                r"This PR is based on an upstream commit that is {} days old.
-
-*It's recommended to update your branch according to the [rustc_dev_guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
-                days_old
-            ));
-        }
-        Ok(None) => {
-            // Parent commit is not too old, log and do nothing
-            log::debug!("PR #{} parent commit is not too old", event.issue.number);
-        }
-        Err(e) => {
-            // Error checking parent commit age, log and do nothing
-            log::error!(
-                "Error checking parent commit age for PR #{}: {}",
-                event.issue.number,
-                e
-            );
-        }
-    }
-
-    None
-}
-
-/// Checks if the PR's parent commit is too old.
-///
-/// This determines if a PR needs updating by examining the first parent of the PR's head commit,
-/// which typically represents the base branch commit that the PR is based on.
-///
-/// If this parent commit is older than the specified threshold, it suggests the PR
-/// should be updated/rebased to a more recent version of the base branch.
-///
-/// Returns:
-/// - Ok(Some(days_old)) - If parent commit is older than the threshold
-/// - Ok(None)
-///     - If there is no parent commit
-///     - If parent is within threshold
-/// - Err(...) - If an error occurred during processing
-pub(super) async fn is_parent_commit_too_old(
-    commit: &GithubCommit,
-    repo: &Repository,
-    client: &GithubClient,
-    max_days_old: usize,
-) -> anyhow::Result<Option<usize>> {
-    // Get the first parent (it should be from the base branch)
-    let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else {
-        return Ok(None);
-    };
-
-    let days_old = commit_days_old(&parent_sha, repo, client).await?;
-
-    if days_old > max_days_old {
-        Ok(Some(days_old))
+    if days_old > age_threshold {
+        log::info!(
+            "PR #{} has a parent commit that is {} days old",
+            event.issue.number,
+            days_old
+        );
+
+        Some(format!(
+            r"This PR is based on an upstream commit that is {} days old.
path: src/handlers/check_commits/behind_upstream.rs
user: login: Kobzol
body: We could make `upstream commit` be a link to the merge base, to show people the old master commit.
created_at: 2025-05-23T06:40:41Z
author_association: MEMBER
diff_hunk: @@ -8,95 +8,32 @@ pub(super) const DEFAULT_DAYS_THRESHOLD: usize = 7;
 pub(super) async fn behind_upstream(
     age_threshold: usize,
     event: &IssuesEvent,
-    client: &GithubClient,
-    commits: &Vec<GithubCommit>,
+    compare: &GithubCompare,
 ) -> Option<String> {
     log::debug!("Checking if PR #{} is behind upstream", event.issue.number);
 
-    let Some(head_commit) = commits.first() else {
-        return None;
-    };
+    // Compute the number of days old the merge base commit is
+    let commit_date = compare.merge_base_commit.commit.author.date;
+    let now = chrono::Utc::now().with_timezone(&commit_date.timezone());
+    let days_old = (now - commit_date).num_days() as usize;
 
     // First try the parent commit age check as it's more accurate
-    match is_parent_commit_too_old(head_commit, &event.repository, client, age_threshold).await {
-        Ok(Some(days_old)) => {
-            log::info!(
-                "PR #{} has a parent commit that is {} days old",
-                event.issue.number,
-                days_old
-            );
-
-            return Some(format!(
-                r"This PR is based on an upstream commit that is {} days old.
-
-*It's recommended to update your branch according to the [rustc_dev_guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).*",
-                days_old
-            ));
-        }
-        Ok(None) => {
-            // Parent commit is not too old, log and do nothing
-            log::debug!("PR #{} parent commit is not too old", event.issue.number);
-        }
-        Err(e) => {
-            // Error checking parent commit age, log and do nothing
-            log::error!(
-                "Error checking parent commit age for PR #{}: {}",
-                event.issue.number,
-                e
-            );
-        }
-    }
-
-    None
-}
-
-/// Checks if the PR's parent commit is too old.
-///
-/// This determines if a PR needs updating by examining the first parent of the PR's head commit,
-/// which typically represents the base branch commit that the PR is based on.
-///
-/// If this parent commit is older than the specified threshold, it suggests the PR
-/// should be updated/rebased to a more recent version of the base branch.
-///
-/// Returns:
-/// - Ok(Some(days_old)) - If parent commit is older than the threshold
-/// - Ok(None)
-///     - If there is no parent commit
-///     - If parent is within threshold
-/// - Err(...) - If an error occurred during processing
-pub(super) async fn is_parent_commit_too_old(
-    commit: &GithubCommit,
-    repo: &Repository,
-    client: &GithubClient,
-    max_days_old: usize,
-) -> anyhow::Result<Option<usize>> {
-    // Get the first parent (it should be from the base branch)
-    let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else {
-        return Ok(None);
-    };
-
-    let days_old = commit_days_old(&parent_sha, repo, client).await?;
-
-    if days_old > max_days_old {
-        Ok(Some(days_old))
+    if days_old > age_threshold {
+        log::info!(
+            "PR #{} has a parent commit that is {} days old",
+            event.issue.number,
+            days_old
+        );
+
+        Some(format!(
+            r"This PR is based on an upstream commit that is {} days old.
path: src/handlers/check_commits/behind_upstream.rs
user: login: xizheyin
body: Yeah, it will be more convenient for the author to check the mistakes. :)
created_at: 2025-05-23T07:53:09Z
author_association: MEMBER

PR comments:
user: login: Urgau
created_at: 2025-05-23T10:07:31Z
body: I may not be avalaible this week-end, I will merge it now, as to fix the false positives, and fix the nits in a follow-up PR.
author_association: MEMBER
user: login: xizheyin
created_at: 2025-05-23T12:48:34Z
body: I would like to fix the nit if you are not free. I just finished my vocation.
author_association: MEMBER
user: login: Urgau
created_at: 2025-05-23T12:51:39Z
body: Sure, go ahead.
author_association: MEMBER