DEV Community

Wilson Xu
Wilson Xu

Posted on

How I Built an AI-Powered Bounty Hunting System for GitHub Issues

How I Built an AI-Powered Bounty Hunting System That Finds and Solves GitHub Issues Automatically

Open source bounties are real money. Platforms like Algora, Expensify's Help Wanted program, and dozens of smaller bounty boards collectively post hundreds of issues per week with cash rewards attached -- typically $250 to $5,000 per issue. The problem is not supply. The problem is that by the time you manually find an interesting bounty, read the issue, clone the repo, understand the codebase, and write a fix, someone else has already submitted a pull request.

So I built a system that automates the entire pipeline: monitoring bounty sources in real time, filtering for issues I can actually solve, analyzing the codebases, and generating candidate fixes. The monitor runs every 10 minutes on a cron job. When it finds something worth pursuing, it pings my phone. When I sit down to work, the solution is often already half-written.

This article walks through the architecture, the actual code, the real bounties I have pursued, and honest numbers on what this approach earns.

Prerequisites

To follow along, you will need:

  • Node.js 18+ (for built-in fetch support)
  • GitHub CLI (gh) installed and authenticated -- run gh auth status to verify
  • A macOS, Linux, or WSL environment for cron scheduling
  • Basic familiarity with the GitHub API and JavaScript/TypeScript

The Bounty Landscape in 2026

Before diving into code, it helps to understand where the money comes from.

Algora is the largest bounty aggregator. Projects post issues with explicit dollar amounts from $200 to $5,000. On any given day, 10-15 open bounties above $200 exist in TypeScript, JavaScript, and Python alone.

Expensify runs one of the most consistent programs. Nearly every "Help Wanted" issue in Expensify/App carries a $250 bounty, paid through Upwork after merge. The catch: a Contributor+ reviewer must approve your proposal before you submit a PR.

AsyncAPI, Supabase, Cal.com, and others run formal bounty programs. $100-$500 per issue is typical for mid-complexity bugs.

GitHub Issues with label:bounty is the long tail. Individual maintainers attach bounties across thousands of repositories. Harder to find, but less competitive.

The total addressable market is meaningful side income if you can reduce time per bounty from 4-8 hours of manual work to 30-60 minutes of review and refinement.

Architecture: Monitor, Filter, Solve

The system has three stages:

┌─────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│  Bounty Monitor  │────►│  Filter + Score  │────►│   Solver Agent   │
│  (cron, 10 min)  │     │  (lang, amount)  │     │  (AI + codebase) │
└─────────────────┘     └──────────────────┘     └──────────────────┘
        │                        │                        │
   Algora scraper          $200+ minimum            Clone + analyze
   GitHub API search       TS/JS/Python only        Generate patch
   Deduplication           Skip aggregates          Write tests
        │                        │                        │
        ▼                        ▼                        ▼
   bounty-log.json        macOS notification        solution/ dir
Enter fullscreen mode Exit fullscreen mode

Stage 1: The Monitor scrapes Algora's HTML and queries the GitHub Search API for issues labeled bounty created in the last 2 hours. It runs on a 10-minute cron schedule.

Stage 2: Filtering eliminates bounties below $200, bounties in languages I do not work with (Rust, Go, Scala), and duplicate entries. New bounties trigger a macOS notification with the Glass sound -- impossible to miss.

Stage 3: The Solver is a semi-automated step where I point an AI coding agent at the issue. It clones the repo, reads the issue, traces the relevant code paths, and generates a patch with tests. I review, refine, and submit.

Building the Bounty Monitor

The monitor is a single Node.js file with zero external dependencies. No npm install, no build step. It uses Node's built-in fetch and child_process. This matters because you want a monitor that runs reliably on a cron job for weeks without dependency rot.

Here is the configuration that drives everything:

const MIN_BOUNTY_USD = 200;
const ALGORA_URL = "https://algora.io/bounties";
const LOG_PATH = path.join(__dirname, "bounty-log.json");
const RELEVANT_LANGUAGES = new Set([
  "typescript", "javascript", "python",
  "ts", "js", "py", "node", "nodejs",
  "react", "next", "nextjs", "vue", "nuxt",
  "svelte", "deno", "bun", "django", "flask", "fastapi",
]);
const MAX_BOUNTY_AGE_HOURS = 2;
Enter fullscreen mode Exit fullscreen mode

The language set is deliberately broad. A bounty on a Next.js app might say "React" or "Next" rather than "TypeScript." Casting a wide net at the filter stage is better than missing $500 bounties because your regex was too strict.

Scraping Algora Without a DOM Parser

Algora renders bounty cards as server-side HTML. Without jsdom or cheerio (remember, zero dependencies), I parse the HTML with regex. This sounds terrible and it mostly is, but for a monitoring script that just needs dollar amounts and links, it works.

The parser runs two strategies. Strategy 1 looks for HTML chunks that contain both an anchor tag and a dollar amount nearby:

const chunkRegex =
  /(<a[^>]*href="([^"]*)"[^>]*>[\s\S]*?<\/a>[\s\S]{0,500}?\$[\d,]+|(?:\$[\d,]+)[\s\S]{0,500}?<a[^>]*href="([^"]*)"[^>]*>)/gi;
Enter fullscreen mode Exit fullscreen mode

Strategy 2 is a broader sweep that finds any dollar amount on the page and grabs 300 characters of surrounding context:

const broadRegex = /.{0,300}\$\s?[\d,]+(?:\.\d{2})?.{0,300}/gi;
Enter fullscreen mode Exit fullscreen mode

This catches bounties even when the HTML structure is unconventional, but it also catches pricing tables, "total paid" statistics, and subscription costs. So the parser filters out noise:

if (
  lowerSeg.includes("pricing") ||
  lowerSeg.includes("subscription") ||
  lowerSeg.includes("per month") ||
  lowerSeg.includes("total") ||
  lowerSeg.includes("rewarded") ||
  lowerSeg.includes("paid out")
) continue;

// Skip unrealistically large amounts (> $50k likely aggregate stats)
if (amount > 50000) continue;
Enter fullscreen mode Exit fullscreen mode

The deduplication logic prefers entries with specific GitHub URLs over generic Algora links. If both strategies find a $500 bounty but one has the actual GitHub issue URL and the other just points to algora.io/bounties, it keeps the specific one.

Querying GitHub's Search API

For the GitHub source, the monitor uses the gh CLI for authenticated requests (higher rate limits) with a fallback to unauthenticated fetch:

const since = new Date(
  Date.now() - MAX_BOUNTY_AGE_HOURS * 60 * 60 * 1000
).toISOString();

const query = `label:bounty created:>=${since}`;

let result;
try {
  result = execSync(
    `gh api "search/issues?q=${encodedQuery}&sort=created&order=desc&per_page=100"`,
    { encoding: "utf-8", timeout: 30000 }
  );
} catch {
  // Fallback to unauthenticated API
  const resp = await fetch(
    `https://api.github.com/search/issues?q=${encodedQuery}&sort=created&order=desc&per_page=100`
  );
  result = await resp.text();
}
Enter fullscreen mode Exit fullscreen mode

The interesting part is bounty amount parsing. Bounty amounts hide in different places: the issue title ("[$500] Fix the parser"), the body ("This issue has a $500 bounty"), or labels (bounty-500, reward:500, $500). The parser checks all three:

for (const label of issue.labels || []) {
  const labelAmount = parseDollarAmount(label.name);
  if (labelAmount && (!amount || labelAmount > amount)) {
    amount = labelAmount;
  }
  const numMatch = label.name.match(
    /(?:bounty|reward)[\/:\-_]?\s?(\d+)/i
  );
  if (numMatch) {
    const parsed = parseInt(numMatch[1], 10);
    if (parsed >= MIN_BOUNTY_USD && (!amount || parsed > amount)) {
      amount = parsed;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After finding bounties, the monitor fetches each repository's metadata to check the primary language. A bounty on a Rust project is not worth my time even if the issue body mentions "JavaScript" in passing.

Notifications and Persistence

New bounties trigger a macOS notification using osascript. This is deliberately low-tech:

function sendNotification(title, message) {
  try {
    execSync(
      `osascript -e 'display notification "${escapedMsg}" with title "${escapedTitle}" sound name "Glass"'`
    );
  } catch {
    // Notification failure is non-fatal
  }
}
Enter fullscreen mode Exit fullscreen mode

The Glass sound is the key. When I hear it, I know money appeared on the internet. Every other notification on my system is silent.

All seen bounties are persisted to bounty-log.json so re-runs do not spam notifications for the same issue. Entries older than 7 days are automatically purged. The log is capped at 500 entries to keep the file manageable.

The Solver Loop: From Issue to Patch

When a bounty notification fires, here is what happens next. I run a coding agent -- Claude Code, in my case -- pointed at the issue. The process is:

  1. Clone the repository (or pull latest if already cloned)
  2. Read the issue thoroughly, including all comments
  3. Trace the code path mentioned in the issue
  4. Generate a minimal fix
  5. Write tests that cover the fix
  6. Package everything as a patch file

Let me walk through two real examples.

Example 1: Expensify/App #85632 ($250)

The issue: "We'd hate to see you go!" reason field is not mandatory when closing an account. Users could close their Expensify account without providing any feedback in the reason field.

The root cause took 3 minutes to find. In CloseAccountPage.tsx, the form validation function only listed phoneOrEmail as required:

const errors = getFieldRequiredErrors(values, ['phoneOrEmail'], translate);
Enter fullscreen mode Exit fullscreen mode

The fix was one line:

const errors = getFieldRequiredErrors(values, ['reasonForLeaving', 'phoneOrEmail'], translate);
Enter fullscreen mode Exit fullscreen mode

The AI agent found this by searching for getFieldRequiredErrors calls across the codebase, cross-referencing with the form's field definitions, and identifying that reasonForLeaving was defined as a form field but never validated.

This is the sweet spot for automated bounty hunting: issues where the bug is straightforward validation logic, the fix is small, but you need to navigate a large codebase to find the right file. The Expensify/App repo has thousands of files. A human might spend 30 minutes just finding CloseAccountPage.tsx. An AI agent with codebase search finds it in seconds.

Example 2: asyncapi/cli #2027 (Bounty Program)

The issue: CLI hangs indefinitely when --registry-url points to an unreachable host. The original code made a fetch call with no timeout:

try {
  const response = await fetch(registryUrl as string);
  // ...
} catch {
  throw new Error(`Can't fetch registryURL: ${registryUrl}`);
}
Enter fullscreen mode Exit fullscreen mode

The fix added an AbortController with a 5-second timeout, switched from GET to HEAD for lightweight validation, and provided specific error messages for different failure modes:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);

try {
  const response = await fetch(registryUrl, {
    method: 'HEAD',
    signal: controller.signal,
  });
  // ... auth checking
} catch (err: unknown) {
  if (err instanceof Error && err.name === 'AbortError') {
    throw new Error(
      `Registry URL '${registryUrl}' is unreachable (timed out after ${REGISTRY_TIMEOUT_MS / 1000}s).`
    );
  }
  if (err instanceof Error && err.message.includes('registryAuth')) {
    throw err;
  }
  throw new Error(`Can't reach registry at '${registryUrl}'.`);
} finally {
  clearTimeout(timeoutId);
}
Enter fullscreen mode Exit fullscreen mode

This fix is more nuanced than the Expensify one. The AI agent had to understand the difference between a timeout, a network error, and an auth error, and handle each case differently. It also generated comprehensive unit tests covering all three paths.

Running on a Schedule

The monitor runs as a cron job every 10 minutes. On macOS, I use a simple cron entry:

*/10 * * * * /usr/local/bin/node /path/to/bounty-monitor.js >> /path/to/bounty-monitor.log 2>&1
Enter fullscreen mode Exit fullscreen mode

A typical run takes 2-3 seconds. The log shows the pattern:

[2026-03-18T16:00:01.140Z] Bounty Monitor starting...
[2026-03-18T16:00:01.143Z] Fetching Algora bounties...
[2026-03-18T16:00:01.222Z] Fetching GitHub bounty issues (last 2 hours)...
[2026-03-18T16:00:02.089Z] Found 0 GitHub bounties >= $200 in TS/JS/Python
[2026-03-18T16:00:03.137Z] Found 11 Algora bounties >= $200
  RESULTS: 11 bounties found
[SEEN] $5000 — Algora Bounty
       URL: https://github.com/zio/zio/issues/9356
[SEEN] $4000 — Algora Bounty
       URL: https://github.com/archestra-ai/archestra/issues/1301
[NEW]  $2500 — Algora Bounty
       URL: https://github.com/golemcloud/golem-cli/issues/275
Enter fullscreen mode Exit fullscreen mode

Both sources are fetched in parallel using Promise.all, so total runtime is bounded by the slower source (usually Algora at ~2 seconds).

Results and Hard-Earned Lessons

After running this system, here is what I have learned:

Speed is everything. On competitive programs like Expensify, bounties get proposals within hours of posting. Having a 10-minute monitoring loop means I see issues before most people browsing GitHub. But even with early detection, you are racing against other automated hunters.

One-line fixes pay the same as hundred-line fixes. The Expensify validation fix was literally one line of code for $250. The value is not in the code -- it is in finding the right file in a massive codebase and proving your fix is correct with tests.

Most bounties are not worth pursuing. Of the 11 bounties my monitor found in a single scan, only 2-3 were in languages I work with and at a complexity level where AI assistance provides real leverage. The $5,000 ZIO bounty is Scala. The $4,000 Archestra bounty requires deep domain knowledge. Realistic targets are the $200-$500 range in frameworks you already know.

The proposal bottleneck is real. Many bounty programs require a proposal or claim before you write code. Even if your fix is ready, you might wait days for maintainer approval while someone else gets picked. Factor this into your time calculations.

AI-generated fixes need human review. The solver agent gets the fix right about 70% of the time for straightforward bugs. The other 30% it misses edge cases, uses deprecated APIs, or misunderstands the codebase conventions. You cannot submit patches blind.

Tests are your competitive advantage. Most bounty hunters submit minimal fixes. Including comprehensive tests with your PR dramatically increases your chances of getting merged. The AI agent is genuinely good at generating test cases once it understands the fix.

The Economics: Honest Numbers

Let me be direct about the money.

Time investment to build the system: About 6 hours for the monitor, another 4 hours refining the solver workflow. Call it 10 hours total.

Ongoing time per bounty: 30-60 minutes of review, refinement, and submission for issues the solver handles well. 2-4 hours for issues that need significant human reasoning.

Hit rate: Roughly 1 in 4 submissions gets merged. Competition, maintainer responsiveness, and process requirements account for most rejections, not code quality.

Realistic monthly earnings at steady state: $500-$1,500 if you dedicate 5-10 hours per week. This assumes 2-4 successful bounties per month averaging $250-$400 each. Some months will be zero. Some months a $2,000 bounty lands and you feel brilliant.

Is it worth it compared to freelancing? At $500-$1,500/month for 20-40 hours of work, the effective hourly rate is $25-$50. The value proposition is not the hourly rate -- it is that the work builds your open source profile and the monitoring runs while you sleep. Plus, every merged PR earns trust with maintainers. Expensify regulars report completing bounties in under an hour because they already know the codebase.

Quick Start: Get Running in 5 Minutes

If you want to try this yourself, here is the minimal setup:

# 1. Create the project directory
mkdir bounty-monitor && cd bounty-monitor

# 2. Create bounty-monitor.js with the code from this article
#    (single file, zero dependencies)

# 3. Test it manually first
node bounty-monitor.js

# 4. Add to cron (runs every 10 minutes)
crontab -e
# Add this line:
# */10 * * * * /usr/local/bin/node /path/to/bounty-monitor.js >> /path/to/bounty-monitor.log 2>&1

# 5. Verify it is running
tail -f /path/to/bounty-monitor.log
Enter fullscreen mode Exit fullscreen mode

Start with the monitor only. Once you are comfortable triaging bounties manually, layer in AI-assisted solving for the issues that match your skill set.

Conclusion

The code for the bounty monitor is under 500 lines of zero-dependency Node.js. The entire system fits in a single directory. No infrastructure, no servers, no databases. It runs on a laptop cron job and pings your phone when money appears.

The combination of automated monitoring and AI-assisted solving compresses a 4-8 hour manual workflow into 30-60 minutes of review and refinement. You will not get rich, but at $500-$1,500 per month of side income while building your open source profile, the return on a 10-hour setup investment is hard to beat.

That is the best kind of automation: boring, reliable, and profitable enough to justify the afternoon you spent building it.


The bounty monitor source code referenced in this article is available as a single-file Node.js script. If you build on it, I would love to hear what you find.

Top comments (0)