This is part 2 of my series on automated bounty hunting. Part 1 covers how I built the agent that scans GitHub while I sleep.
Everyone talks about GitHub bounties like they're free money waiting to be claimed. The truth is messier. After scanning the full Opire bounty board — all 30 live rewards — here's what the data actually looks like.
What Is Opire?
Opire is a bounty platform layered on top of GitHub. Anyone can add a dollar reward to any issue. When you fix it, you comment /claim in your PR and Opire pays out. No agency, no middleman for the most part.
The API is refreshingly open:
curl -s "https://api.opire.dev/rewards" | jq '.[] | {amount, issue_url}'
No auth. No rate limiting (at least not that I hit). 30 results as of March 22, 2026.
The Script
I wrote a scanner that cross-references the Opire API against the GitHub Issues API to check actual issue state. The problem: Opire doesn't tell you if an issue is already closed. You need to check yourself.
import httpx
import json
def scan_opire_bounties():
resp = httpx.get("https://api.opire.dev/rewards")
rewards = resp.json()
results = []
for reward in rewards:
issue_url = reward.get("issue_url", "")
# Parse owner/repo/number from GitHub URL
parts = issue_url.replace("https://github.com/", "").split("/")
if len(parts) < 4:
continue
owner, repo, _, number = parts[0], parts[1], parts[2], parts[3]
# Check actual issue state
gh_resp = httpx.get(
f"https://api.github.com/repos/{owner}/{repo}/issues/{number}",
headers={"Accept": "application/vnd.github+json"}
)
if gh_resp.status_code == 200:
issue = gh_resp.json()
results.append({
"amount": reward.get("amount_usd", 0),
"state": issue["state"],
"title": issue["title"],
"url": issue["html_url"],
"claimers": len(reward.get("claimers", [])),
})
return sorted(results, key=lambda x: x["amount"], reverse=True)
Simple. The key insight: check GitHub state, not just Opire state. Several "live" bounties point to closed issues.
What I Found
Out of 30 bounties:
Already closed (skip these):
- $385 — Zed Helix keymap ❌ closed
- $200 — Leantime collaborators ❌ closed
- $200 — Keycloak IDP token refresh ❌ closed
- $110 — Storybook select control bug ❌ closed
- $50 — restfuncs busboy rewrite ❌ closed (was my top pick — gone before I could claim it)
These bounties are technically still on Opire but the underlying issue is resolved. Dead ends.
Truly open, ranked by value:
| $ | Language | Issue | Claimers |
|---|---|---|---|
| $13,380 | C++ | Godot web platform exports | 3 |
| $1,500 | C | zeroperl Async Web APIs | 0 ⭐ |
| $660 | JavaScript | Kokoro German TTS | 2 |
| $200 | C++ | Second Life: replace tut with doctest | 1 |
| $180 | Python | AutoKey Wayland support | 4 |
| $120 | TypeScript | TypeORM: ALTER vs DROP+CREATE columns | 7 |
| $100 | C | FalkorDB path expression crash | 1 |
| $80 | Python | UdioWrapper CAPTCHA handling | 4 |
| $70 | Rust | Deno: test coverage in editor | 0 ⭐ |
| $50 | PHP | PHPUnit empty coverage error msg | 2 |
The Zero-Claimer Trap
The two zero-claimer bounties ($1,500 C and $70 Rust) sound incredible. First mover advantage, no competition. Here's why they're 0:
The $1,500 C bounty (zeroperl) requires implementing async Web APIs at the systems level in C for a WebAssembly runtime. Not a bug fix. Not a feature flag. It's building an async I/O subsystem from scratch. The repo has 3 stars. Nobody has claimed it because the few people who could implement it don't need $1,500.
The $70 Deno coverage bounty requires deep Deno LSP integration — Rust, LSP protocol, VS Code extension surface, and deep understanding of Deno's internal test runner. It's been open for years. The Deno team hasn't even engaged on it. Zero claimers doesn't mean low competition; it means nobody thought it was worth their time.
The lesson: Competition level is a proxy, not a signal. Read the issue.
Where the Real Opportunity Is
The TypeORM bounty ($120, TypeScript) is the most interesting:
typeorm/typeorm/issues/3357 — migration drops and creates columns instead of ALTER
There are 4 open PRs on this issue. None have merged. The maintainer keeps requesting the same thing: the fix needs to work across all database drivers (MySQL, PostgreSQL, SQLite, MSSQL, Oracle). Every PR fixes one driver. The comprehensive fix is still there for the taking.
That's the pattern worth looking for: high competition, but competition that hasn't done the full work.
The Expensify Cautionary Tale
For contrast: I spent a day on Expensify's $250 bounty issues before reading the contribution guide. Expensify has a bot that auto-closes PRs if you haven't been assigned to the issue first. No exceptions.
I submitted three PRs. All three auto-closed within minutes.
The fix: comment on the issue requesting assignment, wait for a maintainer to assign you, then submit the PR with a link to the issue in the format $ https://github.com/Expensify/App/issues/XXXXX.
Process before code. Always.
The Bounty Watcher
The script I'm running now checks Expensify for new unassigned issues every 1-5 minutes and auto-requests assignment:
def get_unassigned_bounties(seen_ids):
result = subprocess.run(
[
"gh", "issue", "list",
"--repo", "Expensify/App",
"--state", "open",
"--json", "number,title,assignees,createdAt",
"--limit", "100",
],
capture_output=True,
text=True,
)
issues = json.loads(result.stdout)
new_bounties = []
for issue in issues:
if issue["assignees"]:
continue
if issue["number"] in seen_ids:
continue
if re.search(r"\[\s*\$\d+\]", issue["title"]):
new_bounties.append(issue)
return new_bounties
The regex \[\s*\$\d+\] matches Expensify's bounty title format like [Bug] Fix this thing [$250]. Simple pattern, real signal.
Rate limiting is baked in: minimum 1 minute between checks, max 3 checks per 5-minute window, randomized within that window so it doesn't look like a bot (even though it is).
What Actually Makes Sense to Target
After the scan, the realistic targets for a TypeScript/Python developer:
$200 — Second Life viewer: replace tut with doctest (C++)
This is a test framework migration, not systems work. tut → doctest is mechanical. The repo is large but the change is isolated. Low-medium competition (1 claimer, 3 trying). A solid PR that migrates all the test files comprehensively would likely win.
$120 — TypeORM ALTER COLUMN (TypeScript)
High competition but no winner. If you know TypeORM's driver architecture, a multi-driver solution is the gap. Worth reading the existing PRs to understand what "complete" looks like to the maintainer.
$50 — PHPUnit empty coverage error (PHP)
Easy. The issue is a user-facing error message improvement. Clear scope, clear solution, low competition. PHP isn't glamorous but $50 for an afternoon is real money.
The Meta Lesson
Bounty hunting is information asymmetry. Most people see the Opire board and think "highest dollar = best target." They chase the $13,380 Godot bounty or the $1,500 C bounty and burn out.
The real edge is reading more carefully than everyone else:
- Is the issue actually open?
- What's the full scope of the fix?
- Why haven't existing PRs merged?
- What specifically does the maintainer want?
The answer to those four questions tells you more than the dollar amount.
All the scanning code and bounty data above came from a live scan on March 22, 2026. Numbers will shift. The approach won't.
If you want to follow the project, I'm building this at @DeekRoumy on GitHub. Full code, full logs, whatever actually works.
Top comments (0)