この度の鉱山閉鎖に関して
これは以前に書いた sbt と西部時代の山師たち という sbt/sbt が Gittensor という名前のクリプト通貨ベースのオープンソース懸賞プログラムに追加され、結果として大量の AI ツールを使ったプルリクが何十人ものユーザから来ているという話の後日談だ。とりあえず、金鉱山は閉鎖することにした。
大まかな状況
1月の時点では以下のように報告した:
GitHub のパルス によると、2025年12月25日から 2026年1月25日の一ヶ月で、23人のコントリビュータが 145本のプルリクエストを送って、132本がマージされ、194個のイッシューがクローズされた。この統計には、僕自身のプルリクエストも含まれるが、僕が 10年以上メンテナをやっていてずば抜けて活発な 1ヶ月だったと思う。
現在のパルスを見返してみると、2026年3月18日から 2026年4月18日の一ヶ月で、27人のコントリビュータが 140本のプルリクエストを送って、126本がマージされ、78個のイッシューがクローズされた。統計を見るとまだ結構のプルリクが来ているが、一時期のピークに比べると Gittensor からのプルリクは落ち着いてきていると思う。
前回も書いた通り、コントリビューションの中には興味深いものもあって、一緒に作業するのが楽しい人も数人いる。
ゴミプルリクエスト
しかしながら、プルリクの実効性にかなりばらつきがあるのは想像に難くないだろう:
イッシュー ジュニア組 シニア組 簡単なイッシュー ⛅ ☀️ 複雑なイッシュー ⛈️ ⛅
Gittensor が設けた予防機構もやむなく、やってくるプルリクエストの試行は低品質なものが多い。ここでは、AI ツールのせいだという結論を急ぐことは避けたい。何故なら、知識を持ったシニア開発者が持てば AI ツールはコードを書く様々な側面を支援することができるからだ。というよりも、Scala 言語を使ったことが無い、もしくはそもそもコードを書いた経験が無い人が LLM のサブスクを購入しただけで開発者になれるという魔法のような話は無いということだと思う。ここで「開発者」とは、イッシューがあるときに、それを分析して、イッシューを修正できる人を指す。Contributor’s guide (CONTRIBUTING.md) では、口を酸っぱくしてコードを書く前にまずテストをするように指示を書いた:
重要: ⚠️ プルリクエストは GitHub Action もしくは人間によって (human-in-the-loop) 動作確認すること
広いユーザ層と長い歴史のため、イッシューの全てが正しかったり、現行で当てはまるとは限らない。
- プルリクエストの作業をする前に、コントリビューションが必要とされているかメンテナに確認すること。
- プルリクエストの作業をする前に、報告された問題を GitHub Actions もしくは自分のコンピュータで必ず再現できることを確認すること。
- コードの変更を行った後、必ず変更点がコンパイルして、問題を修正することを確認すること。
これらが無視されたのか、送られてくるプルリクエストの一定数は、現行バージョンでは再現できないイッシューに対する「修正」、もしくは報告されたイッシューを修正しないゴミプルリクエストが占めた。
砂をふるいにかけて虚偽を探す
例えば、プライベートな GitHub プロファイルで名前が Full Stack Developer の「山師」が、[2.x] fix: Forward WarnOnSourceChanges warning to sbt client #9092 というプルリクエストを送ってきた。これは、sbtn が再読み込み警告を表示しないという #6831問題を修正すると主張した。
このプリリクは、プルリクの説明文がしっかり書かれていて、サマリー、箇条書きされた問題とソリューションの説明がある。AI の開示は「N/A」つまり非該当ということで、コードは手で書いたと言っている。diff は簡潔なものだ:
- val loggerOrTerminal =
- name.flatMap(StandardMain.exchange.channelForName(_).map(_.terminal)) match {
- case Some(t) => Right(t)
- case _ => Left(state.globalLogging.full)
- }
+ val loggerOrTerminal = name.flatMap(StandardMain.exchange.channelForName) match {
+ // Non-interactive network clients only consume build/logMessage notifications.
+ // Route these warnings through the logger so they are delivered to sbt -client.
+ case Some(c: NetworkChannel) if !c.isAttached => Left(state.globalLogging.full)
+ case Some(c) => Right(c.terminal)
+ case None => Left(state.globalLogging.full)
+ }
既にあった ClientTest.scala にテストもちゃんと追加してくれた。テストの方が変更点よりも少し長い。一見すると、非常に良くできたファースト・コントリビューションに見える。変更内容が腑に落ちなかったので、急がずに数日寝かせてみた。後で見て気づいたのは、現行の sbt 2.0.0-RC12 では #6831 は再現できないということだ。
もしも、報告されたイッシューが修正済みならば、コントリビュータはそれらしく見える任意のコードを送ってもテストを通過することができる。もしも、僕が立ち止まって「この変更に意味があるのか」と自問しなければ、このプルリクが取り込まれてた可能性が高い。さらに、このような虚偽の変更は攻撃に悪用される可能性もある。
改ざんされたログ
別の「山師」/コントリビュータから送られた、別のプルリクエストで、最近報告された二重スタンバイ問題を修正するというものがある。僕が、手動で修正の確認を行ったかと聞いたところ、その人はログファイルを添付した:
=== BUGGY: Simulating two standBy() calls for 5s setting ===
--- Call #1 (Boot.main direct) ---
[info] [launcher] standing by: 5
[info] [launcher] standing by: 4
[info] [launcher] standing by: 3
[info] [launcher] standing by: 2
[info] [launcher] standing by: 1
一緒に、ログファイルを生成した Python スクリプトが添付されていたので、確認してみる:
def stand_by(duration_s: int, label: str):
"""Simulates xsbt.boot.Boot.standBy()"""
print(f"\n--- {label} ---")
for i in range(duration_s, 0, -1):
print(f"[info] [launcher] standing by: {i}")
time.sleep(0.1) # Sped up for repro; real is 1000ms
つまり、sbt を実行するかわりに、この人は偽のログを生成する Python スクリプトを作ったのだ。この人が不誠実なのか、犯罪的に馬鹿なだけなのかは僕には分からない。
しつこいレビュー要請
Gittensor の「山師」は、多くの場合クリプト通貨の懸賞を主な動機としてプルリクを送っているのだと思う。乱用を防ぐため、「山師」たちは同時に 10個のプルリクしか開いていけないことになっている。これは、一度始めたプルリクは、なるべく早くマージさせたいというインセンティブが発生して、結果的に「レビューしてくれ」というしつこいレビュー要請が、毎日続く。
これだけでもウザいが、自分は sbt をホビープロジェクトとして有志でメンテしているので、自由の時間にこれをやられると二重でウザい。
メンテナビリティの懸念
インセンティブのずれつながりだと、Gittensor の「山師」は、プルリクの大きさで懸賞金が決まるので、大きければ、大きいほど稼げる。テストさえ通れば、決め打ちの変数だろうが return 文だろうが構わない (自然な Scala は、return や null はほとんど使わない)。しかし、彼らが長期的にコードベースのメンテナンスを行うわけではない。
このメンテナビリティの懸念は、通りがかりで送られてくるプルリク全般に言えることだが、AI ツールとクリプト懸賞金が加わると、イッシューを修正する最小の変更点を書いたり、コードをきれいに片付けるプルリクを送るインセンティブはほとんど無い。
コントリビュータをブロックすることの限界
Gittensor の「山師」が来るまでは GitHub にこういう機能があることすら知らなかったが、GitHub には、今後オーガニゼーションにコントリを送ることを禁止するブロック機能がある。ゴミプルリクエストを見つけると、僕はプルリクをクローズして、その「山師」をブロックしてきた。これを行うと、信頼性スコアが減少して、他のプロジェクトに対しても警告となる。
しかし、この方法にも限界がある。第一に、sbt にブロックされて怒り出す「山師」たちがいることだ。Discord チャンネルにやってきて、交渉を始めたり、謝りはじめる人もいる。
これは、僕や他のメンテナにとってもストレスの多い状況だ。オープンソースは、本来は高い信頼に基づいた、助長的な楽しい活動だ。しかし、匿名の「山師」が出てきたことでコントリビュータ候補やコントリビューションを仮想敵として扱う必要がある。sbt は、他の人がソフトウェアを構築するのを任せる、重要なツールチェインの一部として認知されている。また、その一方で、ハンドル名や公開されたプロファイルから推測すると、多くの「山師」はグローバルサウスや、その他の社会的「過小評価」層の人たちも多い。クリプト懸賞金は、生活に影響が出るぐらい大きいものなのかもしれない。そのためか、毎週違う「山師」がやって来る。僕は、人に対して失礼な物言いをするのは好きじゃない。そのため、この状況が続けば、メンテナはストレスでバーンアウトするか、色んな人が sbt に対して怒り出すか、どこかの国家主体が変なコードをシンプルなビルド・ツールに注入することになる。そのため、本日 sbt/sbt を Gittensor から即座に削除するプルリクエストを送った。
エジェンティックな未来図としての Gittensor マイニング
お別れとなったが、多くの山師たちと触れ合えたのは僕にとって新鮮な経験だったし、彼らの多くのコントリビューションには感謝している。良い意味でも悪い意味でも、エジェンティックな未来図を垣間見ることができたと思う。LLM/AI ツールを使って山師たちは、長年配慮されていなかった GitHub イッシュー全体を見回して、機会主義的に数百のイッシューを修正またはクローズしてくれた。また、一見技術的に正しく見える変更点でも虚偽であったり、攻撃ベクトルとなりうることも示唆してくれた。
僕は、sbt ではドキュメンテーションの翻訳1以外では LLM/AI ツールを使っていないが、今後は使用も検討していこうと思う。しかし、当面の所一番楽しみにしているのは sbt のユーザから普通のプルリクエストをもらうことだ。