DEV Community

Cover image for How I built a tool that checks if a GitHub issue already has a PR — and why that's the feature nobody built
Mahendra S
Mahendra S

Posted on

How I built a tool that checks if a GitHub issue already has a PR — and why that's the feature nobody built

GitTrek - Find open source issues and track GitHub badge progress
I kept doing this.

Find a beginner-friendly issue. Fork the repo. Set up the dev environment. Read through the codebase. Start working.

Then check the issue again and see a comment from 2 days ago: "Hey I'm working on this, should have a PR up soon."

Two hours wasted. Every single time.

The weird part? Almost every existing tool for finding open source issues - goodfirstissue.dev, up-for-grabs.net, codetriage - rely on static lists or periodic scrapes. They show you issues, but they don't show you the live status of whether someone is already quietly working on it right now.

So I built GitTrek to fix this.

The core problem: Silent Claiming

When a developer starts working on an issue, they often don't comment or ask for assignment. They just start. But they usually leave one of these "digital traces":

  1. PR Mentions: They mention the issue number in a PR description or commit message (Fixes #847).
  2. Linked Branches: They click "Create a branch for this issue" in GitHub's UI, then open a PR from that branch.

Both leave detectable events in GitHub's GraphQL API that most discovery tools never bother to check.

The two GraphQL events that make it work

GitHub's timelineItems field on issues exposes every event in an issue's history. Two specific types are the key:

CROSS_REFERENCED_EVENT - Fires when someone mentions the issue number (#847) in a PR or commit. The PR is the source of this event.

CONNECTED_EVENT - Fires when a branch linked to the issue becomes a pull request. In this event, the PR is the subject, not the source.

Pro Tip: This is a common GraphQL "gotcha." source for one, subject for the other. Mix them up, and you get null results with zero errors.

The Query

issue(number: $issueNumber) {
  timelineItems(
    first: 25,
    itemTypes: [CROSS_REFERENCED_EVENT, CONNECTED_EVENT]
  ) {
    nodes {
      ... on CrossReferencedEvent {
        source {
          ... on PullRequest {
            number
            state      # OPEN | CLOSED | MERGED
            isDraft
          }
        }
      }
      ... on ConnectedEvent {
        subject {
          ... on PullRequest {
            number
            state
            isDraft
          }
        }
      }
    }
  }
  linkedBranches(first: 3) {
    totalCount
  }
}
Enter fullscreen mode Exit fullscreen mode

The Classification Logic

GitTrek uses this data to color-code your search results instantly:
| Status | Condition |
|---|---|
| 🔴 Active PR | Open non-draft PR exists — high competition |
| 🟡 Someone started | Draft PR linked — proceed carefully |
| 🟡 Branch exists | linkedBranches > 0, no PR yet — early signal |
| ✅ Safe to claim | No linked PRs or branches found |


High Performance: Running checks in parallel

Checking 20 issues means 21 total API calls (1 search + 20 status checks). To keep the UI snappy, GitTrek doesn't block the initial results. We display the issues immediately and then "hydrate" the competition status badges in parallel:

// Fetch fresh data from APIs in the background
// This ensures one badge failure doesn't block the entire dashboard
const settlements = await Promise.allSettled([
  fetch(`/api/github/badges/pull-shark?username=${user}`).then(r => r.json()),
  fetch(`/api/github/badges/starstruck?username=${user}`).then(r => r.json()),
  // ... more badge checks
]);

// Safely extract results even if some failed
const [pullShark, starstruck, ...] = settlements.map(s => 
  s.status === "fulfilled" ? s.value : null
);

// Apply fallback values for failed items
const psData = pullShark || { count: 0 };

Enter fullscreen mode Exit fullscreen mode

I used Promise.allSettled instead of Promise.all for a reason: if one check fails (e.g., due to a repository permission issue or a single-item rate limit), the rest of your dashboard stays functional.

What else GitTrek does

Beyond "Ghost PR" detection, I built GitTrek to be a full companion for open-source growth:

  • Repository Quality Gates: Filter by stars, forks, and whether the repo actually has a CONTRIBUTING.md.
  • Live Achievement Tracking: Track your progress toward Pull Shark, Galaxy Brain, and YOLO badges using live GraphQL calculations.

The best part? It suggests a "Focus Mission". If you're only 2 PRs away from Gold Pull Shark, GitTrek will build a custom search query to help you find the exact issues needed to close that gap.

Try it out

GitTrek is free, open source, and requires no setup for browsing. You only need to connect your GitHub account if you want to track your personalized badge progress.

Live App: gittrek.vercel.app
Source Code: github.com/mahendra-shah/GitTrek


One question for you: Have you ever wasted time on
an issue that was already being worked on? How did you
handle it? Drop it in the comments - curious how common
this actually is.

Top comments (0)