Skip to content

P1 label persists after linked issue is removed or replaced with non-existing issue #610

Description

@chaodu-agent

Bug Description

When a PR is linked to a P1 issue and then the issue reference is removed or replaced with a non-existing issue number, the p1 label remains on the PR. This allows someone to game the priority ranking system.

Reproduction Steps

Step 1: Link PR to real P1 issue
Step 2: pr-labeler runs → copies p1 label to PR
Step 3: Edit PR, replace real issue with non-existing issue (e.g. closes #99999999)
Step 4: pr-labeler runs again → p1 label STAYS (should be removed/reset)

Flow Diagram

┌─────────────────────────────────────────────────────────────────────────┐
│                        EXPECTED BEHAVIOR                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  PR body: "closes #123"         PR body: "closes #99999999"            │
│  (real P1 issue)                (non-existing issue)                   │
│         │                                │                              │
│         ▼                                ▼                              │
│  ┌─────────────┐                ┌─────────────────┐                    │
│  │ pr-labeler  │                │   pr-labeler    │                    │
│  │   runs      │                │     runs        │                    │
│  └──────┬──────┘                └────────┬────────┘                    │
│         │                                │                              │
│         ▼                                ▼                              │
│  ┌─────────────┐                ┌─────────────────┐                    │
│  │ PR gets p1  │                │ PR p1 REMOVED   │  ← should happen   │
│  │ label       │                │ (reset to p2)   │                    │
│  └─────────────┘                └─────────────────┘                    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                        ACTUAL BEHAVIOR (BUG)                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  PR body: "closes #123"         PR body: "closes #99999999"            │
│  (real P1 issue)                (non-existing issue)                   │
│         │                                │                              │
│         ▼                                ▼                              │
│  ┌─────────────┐                ┌─────────────────┐                    │
│  │ pr-labeler  │                │   pr-labeler    │                    │
│  │   runs      │                │     runs        │                    │
│  └──────┬──────┘                └────────┬────────┘                    │
│         │                                │                              │
│         ▼                                ▼                              │
│  ┌─────────────┐                ┌─────────────────┐                    │
│  │ PR gets p1  │                │ 404 error OR    │                    │
│  │ label       │                │ empty issueLabels│                   │
│  └─────────────┘                └────────┬────────┘                    │
│                                          │                              │
│                                          ▼                              │
│                                 ┌─────────────────┐                    │
│                                 │ Fallback keeps  │  ← BUG             │
│                                 │ existing p1!    │                    │
│                                 └─────────────────┘                    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Root Cause

Two issues in src/copy-issue-labels.ts:

1. No error handling for non-existing issues

issueLabels() calls the GitHub API without try/catch. A non-existing issue returns a 404, which causes Promise.all() to reject, aborting the entire operation — no labels are modified.

// This throws 404 for non-existing issues, crashing the whole flow
const issueLabels = new Set(
  (await Promise.all(references.map((issue) => this.issueLabels(issue)))).flat(),
);

2. Fallback logic preserves existing PR priority

Even if the 404 were handled gracefully, highestPriorityLabel() falls back to the PR's current label:

private highestPriorityLabel(issueLabels, pullLabels): string {
  return this.priorityLabels.find(l => issueLabels.has(l))  // 1. from issue
    ?? this.priorityLabels.find(l => pullLabels.has(l))      // 2. keep PR existing ← BUG
    ?? this.priorityLabels[this.priorityLabels.length-1];    // 3. default lowest
}

Security Concern

This can be exploited to artificially inflate PR priority in ranking/dashboards without detection — a user links a P1 issue, gets the label, then swaps to a bogus issue reference.

Suggested Fix

  1. Wrap issueLabels() in try/catch — return [] for non-existing issues
  2. When no valid linked issues exist (all references are invalid), reset priority to lowest rather than preserving the current PR label

Reported from: aws/aws-cdk repo usage of this action in .github/workflows/pr-labeler.yml

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions