Skip to content

feat: Apply status:stale and status:stale-reviews labels automatically#49

Merged
carolinerg1 merged 15 commits into
Universal-Commerce-Protocol:mainfrom
damaz91:triage-blocked-stale
Jul 22, 2026
Merged

feat: Apply status:stale and status:stale-reviews labels automatically#49
carolinerg1 merged 15 commits into
Universal-Commerce-Protocol:mainfrom
damaz91:triage-blocked-stale

Conversation

@damaz91

@damaz91 damaz91 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Add logic to the triage tool to:

  • Identify PRs with status:blocked label and apply status:stale if they have been in that state with no activity (comments or reviews) for more than 30 days since the label was applied.
  • Identify PRs with status:under-review label and apply status:stale-review if there has been no activity (comments or reviews) in the past 21 days since the label was applied.
  • Enforce mutual exclusivity of status:* labels: when a new status label is applied, any other existing status labels are automatically removed from the PR.
  • Recover stale blocked PRs: if a PR has status:stale and new activity (commits or comments) by the PR author is detected, the tool restores status:under-review (which automatically removes status:stale due to mutual exclusivity).
  • Recover stale review PRs: if a PR has status:stale-review and new activity (reviews or comments) by a member of the GitHub organization (excluding the PR author) is detected, the tool restores status:under-review (which automatically removes status:stale-review due to mutual exclusivity).

Both features share a common helper to calculate stale duration based on activity.

Also updated the README.md to reflect that any label acts as a skip label for the status:needs-triage logic, as the tool now searches for no:label and skips in-memory if the PR has any labels. Documented the new rules in README with simplified tables, mutual exclusivity details, and recovery rules.

Category (Required)

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Related Issues

None

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

@damaz91
damaz91 force-pushed the triage-blocked-stale branch 4 times, most recently from 187e700 to 3dc45a6 Compare July 21, 2026 09:46
@damaz91 damaz91 changed the title triage: Apply status:stale to PRs blocked for > 21 days feat: Apply status:stale and status:stale-reviews labels automatically Jul 21, 2026
@ptiper

ptiper commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Hi Federico,

Many thanks for putting this together! The implementation is very clean, and the unit test coverage is thorough and well-structured.

I went through the logic and compared it with our triage revamp design proposal. I have 2 suggestions:

  1. Author inactivity threshold (30 days vs 21 days):
    In the triage revamp proposal (Pillar D), the author inactivity threshold for status:blocked PRs before applying status:stale is specified as 30 days. The PR currently uses 21 days (BLOCKED_STALE_THRESHOLD_DAYS = 21). Should we update this to 30 days to match the design spec?

  2. status:stale-review and DevOps triage sweep:
    The design proposal mentions that when a PR becomes status:stale-review, status:needs-triage is also applied concurrently so that it re-enters the daily DevOps triage sweep. With the strict mutual exclusivity enforced in _apply_label, applying status:stale-review removes status:needs-triage.
    We should confirm whether we want to allow status:needs-triage to coexist with status:stale-review, or if we prefer keeping strict mutual exclusivity and having the DevOps triage sweep query label:status:stale-review alongside label:status:needs-triage.

Thanks again for the great work on this!

@ptiper
ptiper self-requested a review July 21, 2026 11:33
@ptiper ptiper added the devops label Jul 21, 2026
@damaz91

damaz91 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author
  1. Author inactivity threshold (30 days vs 21 days):
    In the triage revamp proposal (Pillar D), the author inactivity threshold for status:blocked PRs before applying status:stale is specified as 30 days. The PR currently uses 21 days (BLOCKED_STALE_THRESHOLD_DAYS = 21). Should we update this to 30 days to match the design spec?

Addressed 1. For 2., I think keeping each "status" as mutually exclusive makes more sense logically. Aligned to include status:stale-review in the DevOps triage query

damaz91 added 14 commits July 22, 2026 17:30
Add logic to the triage tool to identify PRs with 'status:blocked'
label and apply 'status:stale' if they have been in that state
for more than 21 days.

This is implemented by:
- Querying for 'status:blocked' PRs in bulk triage.
- Checking the timeline events of these PRs to find when the label was applied.
- Applying 'status:stale' if the duration exceeds the threshold.
- Refactoring 'triage_logic.py' to support multiple rules.
- Updating tests and README.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
…y for > 21 days

Add logic to identify PRs with 'status:under-review' label and apply
'status:stale-review' if there has been no activity (comments or reviews)
in the past 21 days since the label was applied.

This is implemented by:
- Querying for 'status:under-review' PRs.
- Finding the label application time.
- Collecting all activity (issue comments, review comments, reviews) after that time.
- Calculating the duration since the latest activity.
- Applying the 'status:stale-review' label if it exceeds 21 days.
- Adding tests and documenting in README.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Update 'status:stale' (blocked) logic to also check for activity
(comments or reviews) in the past 21 days, matching the 'status:stale-review'
logic.

Refactored both rules to use a shared helper `_is_eligible_for_stale_by_label`
to reduce duplication.

Updated tests and README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Update `_apply_label` to identify and remove any existing `status:*`
labels from the PR before applying a new status label.

Added unit tests to verify that:
- Existing status labels are removed when dry-run is False.
- Existing status labels are NOT removed when dry-run is True.
- Non-status labels are ignored.

Updated README.md to document this behavior.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Add logic to identify PRs with `status:stale` label that have new
activity (commits or comments) by the PR author since the label was
applied.

If author activity is detected, the tool restores the `status:blocked`
label (which automatically removes `status:stale` due to mutual
exclusivity).

Updated `triage_all_outstanding` to query for `status:stale` PRs.
Added unit tests for recovery scenarios.
Updated README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Update stale recovery logic to apply `status:under-review` instead
of `status:blocked` when a `status:stale` PR has new author activity.

Added a unit test to verify that `status:under-review` is applied and
`status:stale` is removed.

Updated README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Add logic to identify PRs with `status:stale-review` label that have
new activity (reviews or comments) by anyone other than the PR author
since the label was applied.

If reviewer activity is detected, the tool applies `status:under-review`
(which automatically removes `status:stale-review` due to mutual
exclusivity).

Updated `triage_all_outstanding` to query for `status:stale-review` PRs.
Added unit tests for stale-review recovery.
Updated README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Update `_is_eligible_for_stale_review_recovery` to ensure that the
detecting reviewer activity (comments or reviews) comes from a member
of the GitHub organization (or a collaborator if it is a personal
repository).

Added helper methods `_get_org` and `_is_member` to check membership, with caching.
Updated unit tests to mock organization membership and added tests for non-members and personal repos.
Updated README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
…signed

Update `_triage_needs_triage` to automatically apply `status:under-review`
(and remove `status:needs-triage` due to mutual exclusivity) if the PR
has 1 or more requested reviewers (users or teams) assigned.

Also directly apply `status:under-review` to open, non-draft PRs with no
labels if they already have reviewers assigned, skipping `status:needs-triage`
entirely.

Added unit tests to `TestTriageLabelerNeedsTriageTransitionRules` to cover:
- PR with status:needs-triage and user reviewer -> status:under-review
- PR with status:needs-triage and team reviewer -> status:under-review
- PR with status:needs-triage and no reviewers -> remains unchanged
- Eligible PR (no labels) with reviewers -> status:under-review
- Eligible PR (no labels) with no reviewers -> status:needs-triage

Updated README.md.

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Rename the constant `TARGET_LABEL` (which holds "status:needs-triage") to
`NEEDS_TRIAGE_LABEL` to match the naming convention of other status labels
(e.g., `BLOCKED_LABEL`, `STALE_LABEL`).

TAG=agy
CONV=971925a1-475e-4b95-bd29-a640b0506c53
Cache organization membership checks in TriageLabeler to reduce API calls. Use committer.date instead of author.date for commit activity detection.
Update BLOCKED_STALE_THRESHOLD_DAYS to 30 to match the design spec. Update unit tests and README accordingly.
Update README.md to document all 5 candidate PR categories queried during bulk triage and accurately describe the labels applied/removed in the core triage loop.
@damaz91
damaz91 force-pushed the triage-blocked-stale branch from 4b9165f to 4cddb33 Compare July 22, 2026 17:30
@carolinerg1
carolinerg1 merged commit c17f3b6 into Universal-Commerce-Protocol:main Jul 22, 2026
12 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.

3 participants