I nearly closed the tab at the line that said "5,000+ GitHub stars."
None of my public repositories come anywhere near that. As an indie developer I ship apps to the App Store and Google Play, and the repos I keep public are small tools carved out of that work — two or three digits of stars, at best. My reflex was that free-credit programs like this belong to somebody else.
Then I kept reading. The star count turns out to be one entrance of two, and there is a second door for projects whose weight does not show up in the obvious numbers. Along the way, while checking whether I qualified, I found that my own contribution history was being counted as roughly a third of what it actually was.
Here are both findings, with the commands I ran.
The 5,000 figure is one of two doors
Claude for Open Source gives eligible open source maintainers and contributors six months of Claude Max 20x at no cost. Max 20x runs $200 a month, so six months is roughly $1,200 of value.
There are two ways in.
| Track | What it asks for |
|---|---|
| Numeric threshold | A public repository with 5,000+ GitHub stars, or 1M+ monthly npm downloads — plus commits, releases, or PR reviews within the last three months |
| Ecosystem impact | For maintainers whose projects the ecosystem quietly depends on without the visibility. You apply with a written explanation instead of a number |
The shape of the grant itself is worth knowing before you draft anything.
| Item | Detail |
|---|---|
| Granted to | Individuals. Team sharing is not part of it |
| API credits | Not included. This is the Max subscription, not platform credit |
| After six months | No automatic rollover into a paid plan |
| Cap | Up to 10,000 recipients, reviewed on a rolling basis |
Misreading that first row costs you a whole line of reasoning. I started out imagining how the grant might cover work I share with others, and the words "individual only" ended that idea in one sentence. It also sharpened the real question: what would I spend my own six months on?
Terms and thresholds can change, so check the current wording on the Claude for Open Source page before you apply.
Check the threshold with a list, not from memory
Once you maintain more than a couple of repositories, "which one has the most stars" becomes surprisingly hazy. I could not have told you my own numbers with any confidence.
So I wrote a small script that prints the verdict for every repository at once. It queries the GitHub API and the npm downloads endpoint, then compares against the thresholds.
#!/usr/bin/env python3
"""Check local repositories against the Claude for Open Source numeric thresholds."""
import json
import sys
import urllib.request
STAR_THRESHOLD = 5_000
NPM_THRESHOLD = 1_000_000
UA = {"User-Agent": "oss-grant-check"}
def fetch_json(url):
req = urllib.request.Request(url, headers=UA)
with urllib.request.urlopen(req, timeout=10) as res:
return json.load(res)
def github_stars(repo):
return fetch_json(f"https://api.github.com/repos/{repo}")["stargazers_count"]
def npm_monthly(pkg):
return fetch_json(f"https://api.npmjs.org/downloads/point/last-month/{pkg}")["downloads"]
def judge(stars, downloads):
reasons = []
if stars is not None and stars >= STAR_THRESHOLD:
reasons.append(f"GitHub {stars:,} stars >= {STAR_THRESHOLD:,}")
if downloads is not None and downloads >= NPM_THRESHOLD:
reasons.append(f"npm {downloads:,} dl/month >= {NPM_THRESHOLD:,}")
return reasons
def main(path):
targets = json.load(open(path))
passed = 0
for entry in targets:
repo, pkg = entry.get("repo"), entry.get("npm")
stars = entry.get("stars_fixture")
downloads = entry.get("downloads_fixture")
if stars is None and repo:
stars = github_stars(repo)
if downloads is None and pkg:
downloads = npm_monthly(pkg)
reasons = judge(stars, downloads)
label = repo or pkg
if reasons:
passed += 1
print(f"[OK] {label}: {' / '.join(reasons)}")
else:
print(f"[MISS] {label}: stars={stars} downloads={downloads}")
print(f"\nMet the numeric bar: {passed} / {len(targets)}")
return 0 if passed else 2
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "targets.json"))Targets come from a JSON file. Fill in stars_fixture or downloads_fixture and the script skips the network call and judges on that value, which lets you sanity-check the threshold logic before you point it at anything real.
[
{"repo": "example/big-framework", "stars_fixture": 12400},
{"repo": "example/quiet-lib", "npm": "quiet-lib", "stars_fixture": 380, "downloads_fixture": 2450000},
{"repo": "example/small-tool", "npm": "small-tool", "stars_fixture": 41, "downloads_fixture": 1300}
]Running it:
$ python3 oss_grant_check.py targets.json
[OK] example/big-framework: GitHub 12,400 stars >= 5,000
[OK] example/quiet-lib: npm 2,450,000 dl/month >= 1,000,000
[MISS] example/small-tool: stars=41 downloads=1300
Met the numeric bar: 2 / 3The second line is the one worth staring at. At 380 stars that package looks nowhere near eligible, yet 2.45M monthly npm downloads clears the bar on its own. Judging by a single metric — usually stars, because stars are the number you see first — throws away real chances.
When nothing qualifies, the script exits with code 2:
$ python3 oss_grant_check.py targets_all_miss.json; echo "exit=$?"
[MISS] example/small-tool: stars=41 downloads=1300
Met the numeric bar: 0 / 1
exit=2Encoding the verdict as an exit status rather than a memory means you can rerun the same check in six months without rebuilding the reasoning.
My last 90 days were split into 266 and 34
Alongside the thresholds, the program asks for recent activity. That is where I ran into something I did not expect.
On one of the repositories I maintain, I counted the last 90 days of commits by author:
SINCE=$(date -d '90 days ago' +%Y-%m-%d)
git log --since="$SINCE" --format='%an <%ae>' | sort | uniq -c | sort -rnThe output:
266 Masaki Hirokawa <ma***@example.com>
34 masakihirokawa <ma***@example.com>
1 Claude Lab Bot <bot@example.net>Same email address, two different display names. Masaki Hirokawa and masakihirokawa are both me — one machine had a different git config user.name and I had been committing that way for months without noticing.
To anything doing the counting, those are two people. If a reviewer happened to land on the smaller bucket, I would look like someone who made 34 commits in three months. The real figure was 300. A factor of nine, produced entirely by a config drift.
The fix was a single line in .mailmap at the repository root:
Masaki Hirokawa <ma***@example.com>A lone name-and-email pair rewrites the display name for every commit carrying that address. Use the capitalized %aN and %aE placeholders and git applies .mailmap while formatting:
git log --since="$SINCE" --format='%aN' | sort | uniq -c | sort -rn 300 Masaki Hirokawa
1 Claude Lab Bot266 and 34 became 300. The bot keeps its own identity, so human work and automated work stay separable — which matters if you plan to quote the number. Writing "300 commits in the last 90 days" is only worth anything if you can explain where the figure came from.
None of this is specific to one program. The same split distorts your GitHub contributor graph and any other review that counts by author. The most useful thing I got out of today was not the eligibility answer; it was noticing a gap in my own records while trying to produce one.
What the rest of us can put in the application
For those of us who miss the numeric bar, the ecosystem impact track is a piece of writing, which means the content of that writing is the whole game.
What I aimed for was verifiable fact rather than assertion:
- Who depends on it. Names of dependent repositories, dependent counts, issue threads where someone wrote "our build breaks without this" — anything a reader can go and check
- What breaks if it stops. Concrete consequences: no alternative exists, or migration costs a known amount of work. Not a general claim about importance
- What you keep doing. Commits in the last 90 days, release cadence, median time to first response on issues. All of it comes out of
git log - What six months would buy. The grant is a means, so name the backlog you intend to clear. Citing actual issue numbers tightens the whole thing considerably
Point three is where the previous section pays off. Before you quote a number, confirm that the number represents your work. Had I skipped that step, I would have presented myself at roughly a third of my actual activity.
I am not in a position to claim broad ecosystem dependence, so if I apply, I plan to describe things exactly as they stand. If that is not enough, it means the work has not accumulated enough yet — which is a reasonable thing to learn, and a clear target for the next six months.
What you can do today
To keep this from being something you only read about:
- Count the last 90 days by author with
git log --sinceand look for display-name drift - If it is split, add one
.mailmapline and recount with%aN - Run the checker across your repositories and get a pass/fail list instead of a guess
- If nothing qualifies, gather the dependency evidence and draft the ecosystem-impact case
One timing note: the weekly usage bump of 50% for Claude Code subscribers runs through August 19. If you have heavy parallel work queued, spending that is more certain than waiting on an application outcome.
Whether or not you apply, "do my records represent what I actually did?" turned out to be a question worth answering. Thank you for reading.