「GitHub スター5,000以上」という一行を見て、ブラウザのタブを閉じかけました。
私が公開しているリポジトリで、その桁に届くものはひとつもありません。個人開発で App Store と Google Play にアプリを出しながら、その周辺で作った小さなツールを切り出して置いている程度で、スターは二桁か三桁です。無償枠の話は自分とは別の世界のもの、と反射的に判断しかけました。
けれど条件の続きを読むと、数値の枠はあくまで入口のひとつでした。もうひとつ、数字では表れにくい依存を持つプロジェクトのための道が用意されています。そして条件を確かめる過程で、自分のリポジトリの活動実績が実際より少なく数えられていることに気づきました。
今日はその二点を、手元で確かめた手順とともに残しておきます。
5,000という数字は、二本ある道の片方でしかありません
Claude for Open Source は、対象となるオープンソースのメンテナ・コントリビュータに Claude Max 20x を6か月間、無償で提供する枠組みです。Max 20x は月額200ドルなので、6か月で1,200ドル相当になります。
応募の入口は二つに分かれています。
| 入口 | 求められるもの |
|---|---|
| 数値基準の枠 | 公開リポジトリで GitHub スター5,000以上、または npm 月間ダウンロード100万以上。加えて直近3か月にコミット・リリース・PR レビューなどの活動があること |
| エコシステムへの影響で申請する枠 | 数値は届かないが、エコシステムが静かに依存している基盤的なプロジェクトを維持している場合。その事情を文章で説明して応募する |
付与される内容にも、あらかじめ把握しておいたほうがよい線引きがあります。
| 項目 | 内容 |
|---|---|
| 付与単位 | 個人単位。チームでの共有は対象外 |
| API クレジット | 付きません。Max のサブスクリプションとしての提供です |
| 期間終了後 | 6か月後に自動で有料プランへ移る仕組みはありません |
| 受け入れ上限 | 最大1万人。審査は順次行われます |
ここを読み違えると、チームの共同利用を前提に申請を組み立ててしまいます。私は最初、複数人で回している作業に充てられないかと考えましたが、個人単位という一行でその案は消えました。かえって迷いがなくなり、自分自身の開発時間に何を充てるかという問いに絞れました。
条件と提供内容は変わりうるので、応募前にはClaude for Open Source の案内ページで最新の記載を確認してください。
数値基準に届いているかは、目視ではなく一覧で出す
複数のリポジトリを持っていると、「どれが一番スターが多かったか」は意外と記憶があいまいになります。私も自分の公開リポジトリのスター数を正確には言えませんでした。
そこで、判定を一覧で出す小さなスクリプトを書きました。GitHub API と npm のダウンロード API を叩いて、しきい値と突き合わせるだけのものです。
#!/usr/bin/env python3
"""Claude for Open Source の数値基準に、手元のリポジトリが届いているかを判定する。"""
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"\n数値基準を満たした対象: {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"))対象は JSON で渡します。stars_fixture と downloads_fixture を書いておくと通信せずにその値で判定するので、しきい値の挙動を先に確かめられます。
[
{"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}
]手元で走らせた結果です。
$ 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
数値基準を満たした対象: 2 / 3二番目の行が、この枠組みの読み方をよく表しています。スター380では数値基準に届かないように見えますが、npm の月間ダウンロードが100万を超えていれば、それだけで入口を通ります。片方の数字だけを見て諦める判断は、もったいないという結論になりました。
どの対象も満たさなかった場合、終了コードは2を返すようにしてあります。
$ python3 oss_grant_check.py targets_all_miss.json; echo "exit=$?"
[MISS] example/small-tool: stars=41 downloads=1300
数値基準を満たした対象: 0 / 1
exit=2判定を人の記憶ではなく終了コードに落としておくと、半年後に再挑戦するときも同じ手順をそのまま回せます。
3か月分の活動が、266件と34件に割れていました
数値基準と並んで、直近3か月の活動実績が問われます。ここで私は予想していなかったものを見ました。
運用しているサイトのリポジトリで、90日分のコミットを著者ごとに数えてみました。
SINCE=$(date -d '90 days ago' +%Y-%m-%d)
git log --since="$SINCE" --format='%an <%ae>' | sort | uniq -c | sort -rn出力はこうなりました。
266 Masaki Hirokawa <ma***@example.com>
34 masakihirokawa <ma***@example.com>
1 Claude Lab Bot <bot@example.net>メールアドレスは同じなのに、表示名が二種類に割れています。Masaki Hirokawa と masakihirokawa、どちらも私です。環境によって git config user.name の設定が違っていたまま、長いあいだ気づかずにコミットを重ねていました。
集計する側から見れば、これは別人です。仮に34件のほうだけを拾われれば、90日で34コミットの人という像になります。実際には300件でした。9倍近い差が、設定の揺れだけで生まれています。
対処は .mailmap の一行で済みました。リポジトリのルートに置きます。
Masaki Hirokawa <ma***@example.com>名前とメールアドレスを一組だけ書くと、そのメールアドレスのコミットの表示名がすべてこの名前に寄せられます。%aN と %aE(大文字)を使うと .mailmap が適用された値で集計されます。
git log --since="$SINCE" --format='%aN' | sort | uniq -c | sort -rn 300 Masaki Hirokawa
1 Claude Lab Bot266と34が300にまとまりました。ボットのコミットは別名のまま残るので、人の作業と自動化の作業を分けて示せます。応募文に「直近90日で300コミット」と書くとき、その数字の出どころを説明できる状態になりました。
この揺れは Claude for Open Source に限った話ではありません。公開リポジトリのコントリビューターグラフや、他の支援プログラムの審査でも同じように効いてきます。今日いちばん実利があったのは、条件を読んだことより、条件を確かめようとして自分の記録の欠けに気づいたことでした。
数字で届かない側が、応募文に書けること
さて、私のように数値基準に届かない場合です。エコシステムへの影響で申請する枠は「文章で説明する」という形なので、何を書くかがそのまま結果を左右します。
書き手として意識したのは、主張ではなく参照可能な事実を並べることでした。
- 誰が依存しているか。依存元のリポジトリ名、パッケージの被依存数、issue で「このライブラリが壊れると動かない」と書かれたやり取りなど、外から確認できるものを挙げます
- 止まると何が起きるか。代替がない、あるいは移行に工数がかかるという具体を、抽象的な重要性の主張に置き換えずに書きます
- 自分が何を続けているか。90日のコミット数、リリース間隔、issue の初回応答までの日数など、
git logから出せる数字が使えます - 6か月で何をするか。無償枠は手段なので、その期間に片づける予定の課題を具体的に書きます。積み残しの issue 番号まで挙げられると、話が締まります
3番目については、前の節のスクリプトと .mailmap がそのまま効きます。数字を書く前に、その数字が自分の作業を正しく表しているかを一度確かめてください。私は確かめずに書いていたら、実績を三分の一以下に見せていました。
私自身は、依存の広さを胸を張って主張できる立場ではありません。ですから応募するとしても、盛らずに現状を書くつもりでいます。通らなければ、通るだけの積み上げがまだ足りなかったという話です。それはそれで、次の半年に向けた素直な指標になります。
今日のうちに動かせること
条件を眺めるだけで終わらせないために、順番を短く置いておきます。
git log --sinceで直近90日の活動を著者別に数え、表示名の揺れがないかを見る- 揺れていたら
.mailmapを一行足し、%aNで集計し直す - 判定スクリプトに自分のリポジトリを並べ、数値基準の合否を一覧で出す
- どれも届かなければ、依存の証拠を集めてエコシステム枠の文面を書く
なお、Claude Code 契約者向けの週次利用枠50%上乗せは8月19日までです。重い並列作業を予定しているなら、応募の結果を待つ前にこちらを使い切るほうが確実だと考えています。
自分の記録が自分を正しく表しているか。応募するかどうかとは別に、確かめておいて損のない問いでした。お読みいただきありがとうございました。