github

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars8,250
mentions255
0197394591788Oct 4Oct 18Nov 2
UTC days · repositories github owns86 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-18

crates.io—
npm52.4M
PyPI—
013.1M26.2M39.3M52.4MOct 4Oct 18Nov 2
Oct 30crates.io: 0/3 packages observed · npm: 3/48 packages observed · PyPI: 0/2 packages observed

Across 53 known packages in repositories github owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete. PyPI excludes known mirrors.

Top repositories

github’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside github’s account where github opened or reviewed pull requests in the past 365 days.

Who noticed github

Everyone who starred or forked github’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

254posts
Nov 37 posts
  1. gh-awの更新を即追従しない:72時間の待機と固定版をPythonで検査する AIエージェントをCIに入れると、アプリの依存関係だけでなく、エージェント本体の更新も運用対象になります。「最新版へ自動更新」と「安全に更新」は同じではありません。更新直後の不具合を避けたい一方、修正版まで一律に待たせると危険が残ることもあります。 GitHub Agentic Workflowsの11月2日付週次更新では、新しく公開されたエンジンの導入を3日待たせる、設定可能なクールダウンが注目PRとして紹介されました。本稿はこの話題を入り口に、公開からの経過時間、固定した版、承認済みハッシュを別々に確認する運用を考えます。 重要なのは日付の区別です。同じ記事が取り上げるv1.0.1のリリース日は10月27日です。11月2日の新リリースではありません。また、週次更新の注目PRに載っているだけで、クールダウンがv1.0.1に同梱されているとは断定しません。設定キーや対応エンジンの網羅性は今回確認できていないため、未確認のYAMLを動く設定として載せない方針です。 以下のコードは、gh-awの内部実装を再現するものではなく、チームで採用条件を合意するための独立したPythonサンプルです。外部API、パッケージ取得、エージェント起動は行いません。 1. 何を待つのか:実行間隔と更新待機は別物 gh-awには既存のon.cooldownもあります。これは前の対象実行が終了してから次のエージェント実行まで間隔を空ける仕組みです。公式トリガー資料では、agentジョブが開始された実行を対象とし、成功・失敗にかかわらず終了から計測します。実行履歴を取得できない場合は実行を許可する、と説明されています。 一方、今回の話題は新しいエンジン版の公開から導入まで待つことです。前者は頻度、後者は依存物の採用時期を扱います。名前が似ていても、on.cooldown: 72hを設定すれば公開直後のパッケージを止められる、とは言えません。 実行間隔を4時間にしても、4時間後に最新パッケージを取れば公開直後の版を採用し得ます。逆に3日経過したエンジンでも、毎分呼べば費用は増えます。 2. 固定すべきものは一つではない 公式の版管理資料は、ローカルやCIで使うgh-aw CLIと、Actionsで動く生成済み.lock.ymlを区別しています。CLIだけ更新しても既存の生成物が同じになるとは限りません。さらに、エンジンCLIの版は別です。用語集のengine.versionはその固定に使う項目で、省略時の既定はlatestと説明されています。 次の組を一つの変更申請として保存する運用を提案します。 生成に使ったgh-awの版と、ソースMarkdownのコミット レビューした.lock.ymlのコミットとActions参照先 採用するエンジンの厳密な版、配布元、対象OS・CPU その配布物の公開時刻、承認済みダイジェスト、確認担当 「1.2.3」という版文字列だけでは配布物を特定できません。同じ版でもOSやCPUでファイルは異なります。今回のサンプルは配布物を一つ選んだ後の判定なので、プラットフォーム選択や取得元の真正性確認は呼び出し側の責任です。 3. 最小コード:経過時間だけで自動許可しない Python 3.12.14、標準ライブラリのみで検証しました。外部依存はありません。policy.pyに次の関数を保存します。架空の版とハッシュを使うテストであり、実在エンジンの安全性評価ではありません。 """Local release-adoption policy; NOT gh-aw's implementation.""" from datetime import datetime, timedelta import re def decide(*, version, published_at, now, sha256, approved_sha256, revoked=False, cooldown_hours=72): if type(cooldown_hours) is not int or cooldown_hours < 0: raise ValueError('cooldown_hours must be a nonnegative integer') if now.tzinfo is None or now.utcoffset() is None: raise ValueError('now must include timezone') if revoked: return 'BLOCK_REVOKED' if not isinstance(version, str) or not re.fullmatch(r'v?\d+.\d+.\d+', version): return 'HOLD_VERSION' if not all(isinstance(s, str) and re.fullmatch(r'[0-9a-f]{64}', s) for s in (sha256, approved_sha256)): return 'HOLD_DIGEST' if sha256 != approved_sha256: return 'BLOCK_DIGEST_MISMATCH' try: released = datetime.fromisoformat(published_at) if released.tzinfo is None or released.utcoffset() is None: return 'HOLD_TIME' except (TypeError, ValueError): return 'HOLD_TIME' if released > now: return 'HOLD_TIME' if now - released < timedelta(hours=cooldown_hours): return 'WAIT_COOLDOWN' return 'ELIGIBLE_FOR_REVIEW' ELIGIBLE_FOR_REVIEWはインストール許可ではなく、次のレビュー工程へ進めるという意味です。取り消し情報を最優先にし、latestやプレリリース、ハッシュ不明、時刻不明は保留します。プレリリースを拒むのはこのサンプルの意図的な制約であり、gh-aw全体の制約ではありません。 また、ハッシュが一致したから安全とも言えません。悪意のあるファイルを承認してしまえば一致します。比較元のapproved_sha256を候補ファイルから自動でコピーしてはいけません。信頼できる配布元と承認記録から別経路で確定させる必要があります。この関数は署名検証器でも、マルウェア検出器でもありません。 4. 実行と境界テスト 添付サンプルを展開し、sampleディレクトリで実行します。 python --version python -m unittest -v テストはネットワークを呼びません。実際に18件を実行し、すべて成功しました。公開から72時間ちょうどなら次のレビューへ進み、1秒前なら待機します。同じ瞬間をUTCとJSTで表した場合も結果は同じです。未来の公開時刻、時差のない日時、不正なハッシュ、失効済み配布物も確認対象に含めました。 最小の境界テストは次の形です。実行時刻を現在時刻に任せず固定するのがポイントです。 from datetime import datetime from policy import decide args = dict( version="1.2.3", published_at="2026-10-31T00:00:00+00:00", now=datetime.fromisoformat("2026-11-03T00:00:00+00:00"), sha256="a" * 64, approved_sha256="a" * 64, ) assert decide(args) == "ELIGIBLE_FOR_REVIEW" args["now"] = datetime.fromisoformat("2026-11-02T23:59:59+00:00") assert decide(args) == "WAIT_COOLDOWN" 日時は表示用の日付ではなく、タイムゾーン付きの公開時刻を使います。ページに「10月31日」としか書かれていないなら、午前0時と推定して72時間を計算しません。本例では不明として保留し、レジストリ等の正式な公開時刻を確認する工程に戻します。 テストで確認したのは自作判定ロジックだけです。gh-awのコンパイル、Actions実行、特定エンジンへの設定反映、モデルの応答品質や速度は未検証です。18件成功をサービス接続成功と読み替えないでください。 5. 既存CIへ入れるなら、更新PRを境界にする まず既存の版を記録し、候補との差分をレビュー用ブランチで確認します。待機判定は更新PRの説明やチェック結果として表示し、その場で本番の依存物を書き換えない形が扱いやすいでしょう。判定結果とともに、使用した公開時刻、確認日時、ハッシュの根拠も残します。 公式資料では生成済みロックファイルを手編集しないよう案内されています。変更対象はソースと管理される依存情報にし、再生成した差分をレビューします。最新のCLIにしただけで、既存ワークフローの実行内容も安全に更新済みと扱わないことが重要です。 採用後は、読み取り専用・小さい対象から動作確認し、エラー率、必要権限、出力先、費用を以前の版と比較します。ここでは測定していないため、削減率や改善幅は示しません。ロールバック先も、単に古い版ではなく、その時点で失効していない承認済み版である必要があります。 6. 費用・認証・データを待機期間で解決しない gh-aw自体は無償のオープンソースですが、公式FAQはエンジンに応じて推論の課金先が異なると説明しています。Actionsの計算資源や外部サービスの料金も、今回のローカルテストとは別です。固定したエンジン版が同じでも、使うモデルや入力長、実行回数が変われば支出は変わり得ます。 本サンプルにはトークン、GitHub認証、APIキーを渡しません。本番組み込み時は、推論の支払主体とリポジトリ操作権限を別々に確認します。単に判定を通すために権限を広げたり、請求先の分からない既存キーを流用したりする設計は避けます。 データ面も独立した確認事項です。コードやIssueをどのモデルへ送るか、ログに何が残るか、保存期間と削除方法、秘密情報の除外を決めます。72時間待っても、機密を送ってよいという根拠にはなりません。判定ログには承認識別子など必要最小限を残し、認証値を記録しない運用を推奨します。 7. 不向きな場面と採用判断 緊急の脆弱性修正に一律72時間を適用する運用は不向きです。待つことのリスクと、直ちに更新するリスクを担当者が比較する例外手順が必要になります。ただし例外を「すべて許可」のスイッチにせず、対象版・理由・期限・承認者を限定します。このサンプルに例外承認機能は実装していません。 公開時刻を信頼できない配布元、内容が差し替わる参照先、時計がずれたCIでも追加対策が必要です。時刻や失効情報を取得できなければ保留する方針は安全側ですが、可用性は下がります。更新待ちの責任者と対応期限まで決めないと、永久に古い版を使い続ける結果にもなります。 採用を勧めたいのは、更新頻度が高く、複数のCIが同じエンジンを利用し、レビュー済み版を説明する必要があるチームです。逆に、一度だけのローカル実験なら台帳運用の方が重い場合があります。目的は72時間という数字を守ることではなく、何を根拠に、どの配布物を、誰が本番へ進めたかを追えるようにすることです。 参照資料 確認日:2026年11月3日(Asia/Tokyo)。公式URLに限定した検索で読めた本文を照合しました。週次更新ページの直接取得は失敗しており、未確認の設定詳細は断定していません。 GitHub Agentic Workflows:11月2日週次更新 公式リリース一覧 Triggers:on.cooldown Releases and Versioning Glossary:Engine Version GitHub Agentic Workflows FAQ

  2. GitHub Copilot App のサンドボックスで .NET アプリを開発するための設定を試してみた GitHub Copilot App のローカルサンドボックスが GA したらしいので、.NET の開発で使えそうかどうか見てみようと思います。 今回は Windows 環境で、コンソールアプリの作成・実行と NuGet パッケージの追加を試し、その後に Minimal API も動かしてみました。 コンソールアプリは動かせましたが、ファイルのアクセス許可を調整したり、NuGet のエラーを調べたりする必要がありました。Minimal API は起動したものの、エンドポイントへのアクセスがタイムアウトし、今回はそこで断念しました。 ワークツリーの保存先を変更している場合の注意 GitHub Copilot App のワークツリーの保存先を D ドライブなどに変更している場合は、デフォルトの C ドライブ配下の場所に戻す必要があります。 現在、Windows のサンドボックスでは、システムドライブ以外にあるフォルダーを作業場所にすると、シェルでフォルダーの一覧取得などができない問題が報告されているためです。以下の Issue でも、D ドライブなどではアクセス拒否になり、システムドライブの C ドライブでは動くことが確認されています。 copilot-cli#5207 コンソールアプリを作成して実行するための設定 GitHub Copilot App にコンソールアプリの作成と実行を依頼したところ、dotnet new console の実行時にアクセス拒否のエラーが出ました。.NET SDK やテンプレートが使うフォルダーへの読み書きを許可する必要がありました。 使ったのは .NET 10 で、global.json で SDK を 10.0.403 に指定しています。 アクセスを拒否されたのは次のパスです。 %USERPROFILE%.dotnet\10.0.403.toolpath.sentinel %USERPROFILE%.templateengine\dotnetcli\10.0.403 %USERPROFILE%.templateengine\packages.json そこで、GitHub Copilot App のサイドバーから Settings を開き、Repositories で対象のリポジトリを選びます。Sandbox にある Additional read/write の Add folder から、次のフォルダーを追加しました。 C:\Users<ユーザー名>.dotnet C:\Users<ユーザー名>.templateengine <ユーザー名> は自分の環境に合わせて置き換えてください。 設定を変更しただけでは実行中のセッションに反映されないので、/restart-session でセッションを再起動する必要があります。 docs.github.com 上記のフォルダーへのアクセスを許可してセッションを再起動した後、Copilot に再実行を依頼すると、dotnet new console でのアプリ作成と、dotnet run での Hello, World! の表示に成功しました。 NuGet パッケージの追加を試してみる コンソールアプリの作成と実行ができたので、続いて Copilot に ReactiveProperty の追加を依頼しました。Copilot が実行したのは次のコマンドです。 dotnet add .\HelloWorld\HelloWorld.csproj package ReactiveProperty ところが、パッケージの追加もアクセス拒否で失敗しました。エラーメッセージには、拒否されたパスが出ていません。 error: Access to the path is denied. NuGet のパッケージ保存先やキャッシュのフォルダーが怪しいと思い、Additional read/write に次の4つを追加しました。 C:\Users<ユーザー名>.nuget C:\Users<ユーザー名>\AppData\Local\Temp\NuGetScratch C:\Users<ユーザー名>\AppData\Roaming\NuGet C:\Users<ユーザー名>\AppData\Local\NuGet 設定を反映して Copilot に再実行を依頼しましたが、それでも Access to the path is denied. が解消しませんでした。 ログを確認すると、次の URL からパッケージの登録情報を取得する GET リクエストには 200 OK が返っていました。少なくとも、この通信には成功しています。 api.nuget.org そこで、Copilot に NUGET_SHOW_STACK を有効にして再実行してもらいました。スタックトレースでは、NuGet.Protocol.HttpCacheUtility.CreateCacheFileAsync から呼ばれた System.IO.FileSystem.MoveFile で失敗していました。登録情報の取得ではなく、キャッシュファイルの移動で止まっていることが分かりました。 フォルダーの許可を追加しても解決せず、ログからもアクセスを拒否されたパスが分からなかったので、Process Monitor で実際のアクセス先を調べることにしました。 Process Monitor で失敗した操作を見る エラーメッセージだけでは先に進めなかったので、Microsoft の Process Monitor(Procmon)を使って、もう一度パッケージ追加を実行したときのファイル操作を確認しました。 learn.microsoft.com そこで見つかったのは、次の場所にあるファイルのリネームの失敗でした。ユーザー名やサンドボックスごとに変わる部分は置き換えています。 %LOCALAPPDATA%\Packages\sandbox.{GUID}\AC\NuGet\v3-cache<source-cache-dir>\list_reactiveproperty_index.dat-new list_reactiveproperty_index.dat-new の名前を、同じディレクトリ内で list_reactiveproperty_index.dat に変えようとしたところ、アクセスを拒否されていました。Procmon では、このリネーム操作(SetRenameInformationFile)の結果が ACCESS DENIED と記録されていました。 実際のアクセス先が AppData\Local\Packages 配下だったので、Additional read/write に次のパスを追加しました。 C:\Users<ユーザー名>\AppData\Local\Packages セッションを再起動して Copilot にパッケージ追加を再実行してもらうと、ReactiveProperty 9.10.0 と依存パッケージの導入に成功しました。値の変更を Subscribe で受け取り、コンソールに出力する処理も動きました。 これで動くようになりました。ただ、ここまでにいくつかのフォルダーを許可しているので、全部必要だったかは確認していません。AppData\Local\Packages を丸ごと許可するのは範囲が広いので、もう少し絞れないかは気になるところです。 私の環境でコンソールアプリを実行できた設定例 試した結果、ユーザー設定の Additional read/write(Folders the sandbox may read and modify)に残っていた .NET 用のパスは次の 7 件です。 C:\Users<ユーザー名>.dotnet C:\Users<ユーザー名>.templateengine C:\Users<ユーザー名>.nuget C:\Users<ユーザー名>\AppData\Local\Temp\NuGetScratch C:\Users<ユーザー名>\AppData\Roaming\NuGet C:\Users<ユーザー名>\AppData\Local\NuGet C:\Users<ユーザー名>\AppData\Local\Packages 設定する場合は、C:\Users<ユーザー名> の部分を自分の環境の絶対パスに置き換えてください。ここでは設定画面で環境変数が展開されるかどうかは確認していないので、絶対パスで記載しています。 これとは別に、添付画像へアクセスするために追加したパスもありました。 C:\Users<ユーザー名>.copilot\workspaces こちらは .NET の実行用ではなく、添付画像へのアクセス用です。 私の環境では、この設定でコンソールアプリの作成・パッケージ追加・実行までできました。もう少し許可するフォルダーを減らせるかもしれませんが、そこまでは試していません。 特に AppData\Local\Packages は、広い範囲の読み書きを許可することになります。今回の実験で使った設定であって、全員にそのまま追加することを勧めるものではありません。どこまで権限を絞っても動くかは未確認です。 Minimal API も試してみる コンソールアプリが動いたので、次は Copilot に Minimal API の作成と実行を依頼しました。アプリは起動しましたが、エンドポイントにアクセスするとタイムアウトしてしまいました。 サンドボックス設定の Outbound internet と Local network は、セッション開始時からどちらも ON にしていました。それでも接続できなかったので、ネットワーク周りの制約を調べてみました。 調べていると、Microsoft の MXC の公開資料で、似た症状の既知の制約を見つけました。Windows の特定の構成では、ホストからサンドボックス内で起動したサーバーへの接続がタイムアウトするというものです。 github.com/microsoft/mxc/blob/main/docs/backends/… リンク先では、Windows build 26691.1002 で、サンドボックスから外への通信をプロキシー経由に限定して試していました。ホストとのループバック通信を許可しても、ホストからサンドボックス内のサーバーへの接続はタイムアウトしたそうです。一方、プロキシーを使わない別の構成では接続できたとのことでした。 今回もこの制約に当たったのかは分かりません。ただ、起動した API に接続できない状態では開発を進められないので、今回はここまでにしました。 まとめ 今回は、サンドボックスのアクセス許可を追加することで、コンソールアプリの作成から NuGet パッケージの追加、実行までできました。ただ、途中で Procmon を使ってアクセス先を調べる必要があり、Minimal API も接続できないところで止まってしまいました。 今回試した Windows のローカル環境では、現時点で自分の実開発に使うにはまだ厳しいと感じました。サンドボックス自体は重要だと思っているので、今後の改善も追っていきたいです。

  3. If you're using GitHub Copilot and Java, I have news for you!! The "Awesome Copilot" marketplace has totally new and updated skills for Java, Spring Boot, Quarkus, JUnit, JPA, TestContainers... Most of them come from my work on Dr JSKill : -> github.com/github/awesome-copilot/pulls

    16 likes · 6 reposts · 794 views
  4. GitHub Actions lockfile: pin your workflows safely A GitHub Actions lockfile is something I have missed for a long time. With npm, Maven or Go it has been normal for years that exactly the version someone reviewed is the one that runs. In workflows, on the other hand, you would find uses: someone/something@v3, and which code sat behind that tag was only decided when the job started. Since October 28, 2026, workflow lockfiles are generally available, and with them you can finally close this gap without unreadable walls of SHAs. In this article we set up the lockfile step by step in a sample repository (a fictional online bookshop), build a check for pull requests that highlights suspicious changes, and look at how to introduce the mandatory policy in your team without seeing every pipeline turn red on Monday morning. > In short > > The new lockfile .github/workflows/actions.lock records the commit and the repository ID for every action and its transitive dependencies, so moved tags can no longer bring unknown code into your pipeline. You create and verify it with the CLI extension gh actions-lock, and Dependabot keeps it up to date. The biggest pitfall are tag-based allowlists for actions: they stop matching after the switch. What you will learn in this article - Which problem the GitHub Actions lockfile solves and which it does not - How to install gh actions-lock and generate your first lockfile - How CI, Dependabot and CODEOWNERS keep the lockfile current and reviewed - How a small TypeScript tool flags suspicious lockfile changes in a PR - How to roll out the “Require lockfile” policy without outages Why tags in workflows are a risk A Git tag is a pointer. Anyone with write access to a repository can move it to a different commit. That is exactly what makes @v3 convenient, because maintainers move the major tag forward with every patch release. At the same time it is the weak spot: if an attacker takes over a maintainer account or a token with write access, v3 may point tomorrow to code nobody on your team has ever seen. Your workflow loads it anyway, with access to secrets, tokens and build artifacts. The classic countermeasure is SHA pinning directly in the workflow, i.e. uses: actions/checkout@<40-character hash>. It works, but it is tedious to read and only protects the first level. If a composite action calls further actions by tag, you are unprotected again at that point. In addition, a hash does not answer whether the repository behind the name is still the same one, for example after a rename when someone registers the old name again. What a lockfile changes here The lockfile separates readability from security. Your workflow keeps the descriptive tag, and the lockfile records which commit it resolved to when it was locked. GitHub also stores the numeric IDs of the owner and the repository. When a run starts, Actions takes the stored commit and checks whether the repository behind the name is still the locked one. A moved tag is not even resolved again during a normal run. One thing matters for the big picture: the lockfile does not judge whether an action is good or bad. It makes sure that what you locked is what runs. If you lock a compromised version, you reliably get the compromised version. Reviewing before locking therefore remains mandatory. !Diagram: workflow reference @v3 is checked via actions.lock against commit and repository ID, moved tags are ignored At start, Actions takes the locked commit and checks the repository ID; a moved tag does not matter. How the GitHub Actions lockfile is structured The file always lives at .github/workflows/actions.lock, it is YAML and it is generated by the CLI extension. You should not edit it by hand. An excerpt for our bookshop looks roughly like this (the hashes are shortened): # .github/workflows/actions.lock # This file is machine-generated by `gh actions-lock`. # Do not edit by hand; run `gh actions-lock` to update. version: 'v0.0.2' workflows: '.github/workflows/ci.yml': - 'actions/checkout@v7.0.1' - 'actions/setup-node@v6.0.0' dependencies: 'actions/checkout@v7.0.1': ref: 'v7.0.1' commit: 'sha1-...' owner_id: 44036562 repo_id: 197814629 'actions/setup-node@v6.0.0': ref: 'v6.0.0' commit: 'sha1-...' owner_id: 44036562 repo_id: 123456789 The workflows section lists which workflow file uses which dependencies. Under dependencies you find, for each reference, the tag (ref), the resolved commit (commit) and both IDs. The IDs of setup-node in this excerpt are placeholders, your file contains the real values. A workflow counts as “onboarded” as soon as it is listed in the lockfile. From then on, every one of its action dependencies must appear there, and Actions runs the stored commit. What the lockfile covers It covers the actions in your workflows and their transitive dependencies, including what a composite action calls internally. Reusable workflows are an exception: their dependencies are covered by the lockfile of the repository in which the reusable workflow is defined. If your team maintains shared workflows in a central repository, that is where you should start. Set up the GitHub Actions lockfile step by step Our starting point is a plain CI workflow for the bookshop. It checks out the code, installs Node.js and runs the tests. The action versions here are only examples, use the ones you already rely on today. # .github/workflows/ci.yml name: ci on: pull_request: push: branches: [main] permissions: contents: read jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 22 - run: npm ci - run: npm test Prerequisites - The GitHub CLI gh, signed in via gh auth login, and write access to the repository. - A repository on github.com or in GitHub Enterprise Cloud with data residency. According to its README, the extension does not support GitHub Enterprise Server. - According to the changelog, lockfiles are available on the Free, Team and Enterprise Cloud plans. At the time of writing, some docs pages still carry the preview notice, so details may still change. - Recommended: Dependabot version updates for GitHub Actions are already enabled (more on that shortly). Step 1: Install the extension gh extension install github/gh-actions-lock gh actions-lock --help Later, gh extension upgrade gh-actions-lock fetches new versions. If you work with an AI coding agent, gh skill install github/gh-actions-lock teaches it to regenerate the lockfile itself after workflow changes. That is a convenience, not a control. Step 2: Generate the lockfile In the root directory of the repository, a single command generates the lockfile for all workflows under .github/workflows/. If you want to start small, pass individual files. # all workflows at once gh actions-lock # or just one workflow to begin with gh actions-lock .github/workflows/ci.yml If the extension reports that it skips a workflow because of local actions, a reference of the form uses: ./… could not be resolved. Local paths always start at the repository root, not at the folder of the workflow file. For special cases there are the options --migrate-local-actions and --ignore-local-actions. Step 3: Actually read the diff Now comes the part many people skip. The extension changes more than just the lockfile. For example, it rewrites partial versions like @v7 to the full tag, i.e. @v7.0.1, while the commit stays the same. According to the docs, local references uses: ./… become uses: $/…, because they always belong to the commit that is currently running and need no lockfile entry. Use git diff to look at every changed file and then commit everything together. git diff -- .github/ git add .github/workflows/ git commit -m "ci: lock GitHub Actions dependencies" Step 4: Check your allowlists This is where the most common pitfall hides. If your repository, organization or enterprise restricts the allowed actions, GitHub matches that list against the locked commit SHAs after the switch. An entry like actions/checkout@v6 will then no longer match. Replace such entries with a pattern like actions/checkout@* or with the full SHA before you push the commit. After that, the CI workflow runs as before, just with pinned code. Expected result: a green run, and a new file actions.lock sitting next to your workflows in the repository. Keep the lockfile current A lockfile that is generated only once goes stale quickly. As soon as someone adds or removes an action, gh actions-lock has to run again. Four building blocks make sure this does not depend on anyone’s memory. Dependabot updates the lockfile too When Dependabot bumps the version of an action in an already onboarded workflow, it regenerates the matching lockfile entry in the same pull request, without extra configuration. However, Dependabot does not onboard new workflows by itself, it only reports that onboarding is missing. I like to combine this with a cooldown so that fresh releases can mature for a few days before they reach us. # .github/dependabot.yml version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" cooldown: default-days: 7 Check pull requests automatically The command gh actions-lock --verify writes nothing, but runs the same checks as a normal run and exits with an error if the lockfile no longer matches the workflows. That makes it suitable as a required check. The extension’s repository ships a template; my variant adds a step that writes lockfile changes neatly into the job summary (we build the tool for that in the next section). # .github/workflows/verify-lockfile.yml name: verify-lockfile on: pull_request: push: branches: [main] permissions: contents: read jobs: verify: runs-on: ubuntu-latest env: GH_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v7.0.1 with: persist-credentials: false fetch-depth: 0 - run: gh extension install github/gh-actions-lock --pin v0.2.0 # Fails if actions.lock does not match the workflows - run: gh actions-lock --verify - uses: actions/setup-node@v6 with: node-version: 22 - name: Summarize lockfile changes if: github.event_name == 'pull_request' env: BASE_REF: ${{ github.base_ref }} run: | git show "origin/$BASE_REF:.github/workflows/actions.lock" > "$RUNNER_TEMP/base.lock" 2>/dev/null || true npm ci --prefix tools/actions-lock-review node tools/actions-lock-review/src/cli.ts "$RUNNER_TEMP/base.lock" .github/workflows/actions.lock >> "$GITHUB_STEP_SUMMARY" Two details matter to me here. The workflow deliberately has no paths filter, because a required check that does not even start for some PRs blocks merging. And the name of the target branch reaches the script through an environment variable instead of directly via ${{ }}, so no expression slips into the shell unchecked. After adding this workflow, run gh actions-lock once, because it uses actions too and has to be in the lockfile. Pre-commit hook and CODEOWNERS Locally, a hook helps that checks before every commit whether each dependency has an entry. It works offline and needs no authentication, but it does not verify the pins against GitHub. That remains the job of the CI check. # .git/hooks/pre-commit (make it executable with chmod +x) #!/bin/sh gh actions-lock --verify-local --no-fix So that lockfile changes do not slip through unnoticed, the file belongs in CODEOWNERS. Combined with a branch rule that requires code owner review, someone responsible for the pipeline always looks at new commits. # .github/CODEOWNERS /.github/workflows/actions.lock @bookshop/platform-team > 🎬 Pull request with a changed action – this animated diagram only runs on my site: watch it on sascha-ms.de Make lockfile changes visible in reviews Nobody enjoys reading a YAML diff full of 40-character hashes. The docs name two signals that matter: a changed commit is new code that will run, so treat it like a version bump. A changed owner_id or repo_id should almost never happen and means that a different repository is behind the name. A small TypeScript tool that I keep in the folder tools/actions-lock-review/ highlights exactly these two cases. Create the project The tool runs directly on Node.js 22.18 or newer, which executes TypeScript without a build step as long as only “erasable” syntax such as type annotations is used. The only runtime dependency we need is the YAML parser yaml. mkdir -p tools/actions-lock-review && cd tools/actions-lock-review npm init -y npm pkg set type=module npm install yaml npm install -D typescript @types/node { "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noEmit": true, "allowImportingTsExtensions": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "types": ["node"] }, "include": ["src", "test"] } Read the lockfile The first file reads the lockfile and checks that the fields we rely on are present. Empty text stands for the first pull request, when there is no lockfile in the target branch yet. // tools/actions-lock-review/src/lockfile.ts import { parse } from "yaml"; export interface LockedDependency { ref: string; commit: string; owner_id: number; repo_id: number; } export interface ActionsLock { version: string; workflows: Record<string, string[]>; dependencies: Record<string, LockedDependency>; } // Reads the content of an actions.lock and checks the fields we need. // Empty text means "there is no lockfile yet" (e.g. in the first PR). export function parseLockfile(text: string): ActionsLock { if (text.trim() === "") { return { version: "none", workflows: {}, dependencies: {} }; } const data = parse(text) as Partial<ActionsLock> | null; if (!data || typeof data !== "object") { throw new Error("actions.lock is not a valid YAML object"); } const dependencies = data.dependencies ?? {}; for (const [key, dep] of Object.entries(dependencies)) { if (!dep || typeof dep.commit !== "string" || typeof dep.repo_id !== "number" || typeof dep.owner_id !== "number") { throw new Error(`Entry "${key}" is incomplete (expected commit, owner_id, repo_id)`); } } return { version: String(data.version ?? "unknown"), workflows: data.workflows ?? {}, dependencies, }; } Find and report differences The second file compares the lockfile of the target branch with the one from the pull request and produces a Markdown table that GitHub renders nicely in the job summary. // tools/actions-lock-review/src/diff.ts import type { ActionsLock } from "./lockfile.ts"; export type ChangeKind = "added" | "removed" | "commit" | "identity"; export interface LockChange { key: string; kind: ChangeKind; before?: string; after?: string; } const short = (sha: string) => sha.replace(/^sha1-/, "").slice(0, 12); // Compares the lockfile from the target branch (base) with the one from the PR (head). export function diffLockfiles(base: ActionsLock, head: ActionsLock): LockChange[] { const changes: LockChange[] = []; const keys = new Set([...Object.keys(base.dependencies), ...Object.keys(head.dependencies)]); for (const key of [...keys].sort()) { const oldDep = base.dependencies[key]; const newDep = head.dependencies[key]; if (!oldDep && newDep) { changes.push({ key, kind: "added", after: short(newDep.commit) }); } else if (oldDep && !newDep) { changes.push({ key, kind: "removed", before: short(oldDep.commit) }); } else if (oldDep && newDep) { // Different repository ID behind the same name: this is the dangerous case. if (oldDep.owner_id !== newDep.owner_id || oldDep.repo_id !== newDep.repo_id) { changes.push({ key, kind: "identity", before: `${oldDep.owner_id}/${oldDep.repo_id}`, after: `${newDep.owner_id}/${newDep.repo_id}`, }); } else if (oldDep.commit !== newDep.commit) { changes.push({ key, kind: "commit", before: short(oldDep.commit), after: short(newDep.commit) }); } } } return changes; } const labels: Record<ChangeKind, string> = { added: "new", removed: "removed", commit: "different commit", identity: "DIFFERENT REPOSITORY", }; // Builds a Markdown report you can use as a PR comment or job summary. export function toMarkdown(changes: LockChange[]): string { if (changes.length === 0) { return "No changes to pinned actions."; } const rows = changes.map( (c) => `| ${c.key} | ${labels[c.kind]} | ${c.before ?? "–"} | ${c.after ?? "–"} |`, ); return ["| Action | Change | before | after |", "| --- | --- | --- | --- |", ...rows].join("\n"); } The command line connects both. If it finds a change of the repository ID, it exits with code 1 and the check turns red. New or changed commits are only reported, because they are normal with every Dependabot update. // tools/actions-lock-review/src/cli.ts import { readFile } from "node:fs/promises"; import { parseLockfile } from "./lockfile.ts"; import { diffLockfiles, toMarkdown } from "./diff.ts"; const [basePath, headPath] = process.argv.slice(2); if (!basePath || !headPath) { console.error("Usage: node src/cli.ts <base.lock> <head.lock>"); process.exit(2); } // If the file is missing in the target branch, we treat it as empty. const readOrEmpty = (path: string) => readFile(path, "utf8").catch(() => ""); const base = parseLockfile(await readOrEmpty(basePath)); const head = parseLockfile(await readFile(headPath, "utf8")); const changes = diffLockfiles(base, head); console.log(toMarkdown(changes)); const identity = changes.filter((c) => c.kind === "identity"); if (identity.length > 0) { console.error(`Warning: ${identity.length} action(s) point to a different repository. Please review.`); process.exit(1); } Test it The tests use the built-in Node.js test runner. They cover the normal case, a new commit, a foreign repository ID, the missing lockfile and broken entries. // tools/actions-lock-review/test/diff.test.ts import { test } from "node:test"; import assert from "node:assert/strict"; import { parseLockfile } from "../src/lockfile.ts"; import { diffLockfiles, toMarkdown } from "../src/diff.ts"; const base = parseLockfile(` version: 'v0.0.2' workflows: '.github/workflows/ci.yml': - 'actions/checkout@v7.0.1' - 'bookshop/deploy-action@v2.3.0' dependencies: 'actions/checkout@v7.0.1': ref: 'v7.0.1' commit: 'sha1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' owner_id: 44036562 repo_id: 197814629 'bookshop/deploy-action@v2.3.0': ref: 'v2.3.0' commit: 'sha1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' owner_id: 1001 repo_id: 2002 `); test("no change yields an empty list", () => { assert.deepEqual(diffLockfiles(base, base), []); assert.match(toMarkdown([]), /No changes/); }); test("new commit is detected as a commit change", () => { const head = structuredClone(base); head.dependencies["bookshop/deploy-action@v2.3.0"].commit = "sha1-cccccccccccccccccccccccccccccccccccccccc"; const changes = diffLockfiles(base, head); assert.equal(changes.length, 1); assert.equal(changes[0].kind, "commit"); assert.equal(changes[0].after, "cccccccccccc"); }); test("different repo_id behind the same name is an identity finding", () => { const head = structuredClone(base); head.dependencies["bookshop/deploy-action@v2.3.0"].repo_id = 9999; const changes = diffLockfiles(base, head); assert.equal(changes[0].kind, "identity"); assert.match(toMarkdown(changes), /DIFFERENT REPOSITORY/); }); test("missing lockfile in target branch: everything is new", () => { const changes = diffLockfiles(parseLockfile(""), base); assert.deepEqual(changes.map((c) => c.kind), ["added", "added"]); }); test("incomplete entries are rejected", () => { assert.throws(() => parseLockfile("dependencies:\n 'x/y@v1':\n ref: 'v1'\n"), /incomplete/); }); npx tsc --noEmit node --test test/*.test.ts node src/cli.ts base.lock ../../.github/workflows/actions.lock Expected result: five green tests and a table like | actions/checkout@v7.0.1 | different commit | aaaaaaaaaaaa | cccccccccccc |. If it says “DIFFERENT REPOSITORY”, look into where the action comes from before you merge anything. Roll out the “Require lockfile” policy Up to this point, the lockfile is voluntary. Repository, organization or enterprise administrators can make it mandatory with the “Require lockfile” policy. It blocks runs of workflows that use actions but are not onboarded to the repository’s lockfile. You find the setting among the rules that govern workflow execution. I find the order GitHub recommends sensible and would adopt it exactly like this: 1. Pilot: Apply the policy only to repositories and workflows that already have a lockfile. 2. Observe: Start in “Evaluate” mode and analyze the results under “Policy insights”. According to the docs, this mode is only available in GitHub Enterprise. 3. Automate: Set up Dependabot, the verify check and the hook so nobody has to run the lock command manually. 4. Enforce: Only then switch to “Active” and include all onboarded repositories. The verify check stays active even after enforcement. The policy stops runs without a valid lockfile, while the check warns the author of a pull request beforehand. Without it, you only find out after the merge that the pipeline is stuck. When a tag moves legitimately If a tag like v7 or a branch like main points to a new commit after a regular release, gh actions-lock --relock brings the pins up to date. If the originally locked commit can no longer be reached upstream, for example after a force push, that fails as well. Only --relock --accept-moved accepts the new commit. This option never belongs in automation, so look at the history of the foreign repository first. # pick up regular releases gh actions-lock --relock # only after a manual review: replace a vanished commit gh actions-lock --relock --accept-moved Common mistakes and how to avoid them | Symptom | Cause | Fix | | --- | --- | --- | | Workflow is skipped during generation | Local action ./… given relative to the workflow folder | Write the path from the repository root and regenerate | | Actions are rejected after the switch | Allowlist contains tags like @v6 | Use a pattern with @* or the full SHA | | Required check hangs on “Expected” | Verify workflow has a paths filter | Remove the filter or do not mark the check as required | | --relock fails | Locked commit was removed upstream | Check the history, then deliberately use --accept-moved | | Rate limit during verify | Anonymous requests to github.com (60 per hour) | Set GH_TOKEN, as in the workflow above | The whole setup costs very little: the verify job is a short run on a standard runner, and in private repositories it counts towards your normal Actions minutes. In terms of security, the basic rule from above applies: the lockfile protects against surprising changes, while reviewing new versions remains human work. My take I consider the GitHub Actions lockfile one of the most useful GitHub features this year, precisely because it is unspectacular. It is the same principle we have long used with npm with a lockfile and a cooldown, finally applied to the pipeline, which often has more permissions than the application itself. Readability and security are no longer played off against each other, and that removes a lot of friction in teams. I would push back on the feeling that this settles the supply chain topic for workflows. If you lock an action without looking at it, you have merely frozen the problem. Keeping the permissions of the GITHUB_TOKEN small, handing out secrets sparingly and choosing third-party actions consciously remain just as important as before. My advice: start in the repository that holds your shared workflows, and give yourself half an hour for the first diff. It rarely takes more. If you want to go one step further after pinning your actions: in GitHub Agentic Workflows: Issue Triage Step by Step, I show how an AI agent in Actions sorts issues with read-only permissions. FAQ Do I still need to pin actions by SHA in the workflow? According to the GitHub docs, SHA pinning in the workflow is no longer necessary once a repository uses the lockfile. You can keep readable tags like @v7.0.1, and the commit lives in the lockfile. Which GitHub plans include workflow lockfiles? According to the changelog from October 28, 2026: Free, Team, Enterprise Cloud and Enterprise Cloud with data residency. The “Evaluate” mode of the mandatory policy is only available in GitHub Enterprise, and the CLI extension does not support GitHub Enterprise Server. Does the lockfile also cover reusable workflows? Only indirectly. The dependencies of a reusable workflow are locked by the lockfile of the repository in which that workflow lives. That is why you should switch central workflow repositories first. May I edit actions.lock by hand? No. If an entry looks wrong, fix the workflow files and regenerate the lockfile with gh actions-lock. Your next step Pick a single repository today, ideally the one with your shared workflows: install the extension, run gh actions-lock, read the diff calmly, check the allowlist, commit. The verify check and Dependabot follow in the second pull request. You can find more posts on secure pipelines in the English blog archive. I am curious: do you already pin your actions by SHA today, or do you rely on tags? And what has kept you from changing that so far? Let me know in the comments. Sources - GitHub Changelog: GitHub Actions workflow lockfiles are generally available (Oct 28, 2026) - GitHub Docs: Workflow dependency lockfiles - GitHub Docs: Locking action dependencies in a repository’s workflows - GitHub Docs: Enforcing lockfiles for action dependencies - github/gh-actions-lock (README, MIT license) - GitHub Docs: Dependabot options reference (cooldown) - Tested with Node.js 22.23, TypeScript 7.0.2 (tsc --noEmit), yaml 2.9.1 and the Node test runner (5 tests). The YAML files were parsed for valid syntax; the workflows themselves and gh actions-lock were not executed. This post was created with AI assistance. --- 👉 Next step: Not sure which AWS service fits your use case? My AWS service finder asks a few questions and points you to the right one. Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de

    3 reactions · 2 comments
  5. @Lina_Hoshino it's funny because the default github .gitignore is different github.com/github/gitignore/blob/main/Global/Agen… means that it was added manually t.co

    8 likes · 1,329 views
  6. actions.lock を試した。タグの force push は防げたが、別リポジトリの再利用ワークフローと --relock は素通りした 2026 年 10 月 28 日に GA になった GitHub Actions のロックファイル(actions.lock)を、自分で用意したアクションのタグを実際に force push して試しました。 - ロックしたワークフローは、タグを別のコミットに動かされても、ロックしたコミットのまま動きました。アクションの中で使っているアクションも同じです。 - タグを、ロックしたコミットから辿れない履歴に付け替えられると、ワークフローはエラーで止まり、どちらのコードも動きませんでした。 - 一方で、次の 3 つは素通りしました。 - ロックファイルに載っていない新しいワークフローは、警告なしで動かされた先のコミットを実行した - 別のリポジトリの再利用ワークフロー(jobs.<id>.uses)は、ロック済みのワークフローから呼んでも、動かされた先を実行した - gh actions-lock --relock は「タグが動いた」と警告を出したうえで、動かされた先のコミットにロックを進め、終了コード 0 で終わった actions.lock を入れようとしている人と、入れたあとの更新や CI での確認の手順を決めたい人向けです。試した手順、結果、入れるときにやることをまとめました。 actions.lock の紹介と導入手順は GitHub Actions で checkout@v7 のようなバージョン指定を書くのはやめよう!時代は actions.lock! に分かりやすくまとまっています。この記事は、その先の「実際に攻撃されたときに何が起きるか」を確かめたものです。 対象と前提 - gh actions-lock 拡張 v0.4.0、ロックファイルの形式 v0.0.3 - 2026 年 10 月 31 日、GitHub.com の Free プラン、ubuntu-24.04 のランナー - gh actions-lock は手元では動かさず、使い捨てのランナーの上で動かしました - 試したリポジトリは公開しているので、実行のログもそのまま見られます - アクションの側(タグを force push する): OTA-EITA/actions-lock-lab-action - 使う側(ロックファイルを置く): OTA-EITA/actions-lock-lab 背景には、2026 年 3 月 19 日に aquasecurity/trivy-action の 77 個のバージョンのタグのうち 76 個が、認証情報を盗むコードに force push で書き換えられた事件があります(GHSA-69fq-xp46-6x23)。タグで指定していたワークフローは、約 12 時間、書き換えられたコードを動かしていました。 試したこと アクションの側は、OUTER_MARKER を出す composite アクションです。中で同じリポジトリの inner@v1 を呼び、こちらは INNER_MARKER を出します。どのコミットが動いたかは、この 2 つの値で分かります。 runs: using: composite steps: - shell: bash run: echo "OUTER_MARKER=A" - uses: OTA-EITA/actions-lock-lab-action/inner@v1 使う側は、ただこのアクションを呼ぶだけのワークフローです。 on: workflow_dispatch permissions: {} jobs: run: runs-on: ubuntu-24.04 steps: - uses: OTA-EITA/actions-lock-lab-action@v1 手順は次のとおりです。 1. 版 A(289fb71)に v1 のタグを付ける 2. 使う側で gh actions-lock を実行し、できたロックファイルを commit する 3. 版 B(875663c、A の上に積んだコミット)を作り、v1 を force push で B に動かす 4. ワークフローを動かし、どのコミットが動いたかを見る ロックファイルはこうなりました(一部)。 version: 'v0.0.3' workflows: '.github/workflows/use-action.yml': - 'ota-eita/actions-lock-lab-action@v1' dependencies: 'ota-eita/actions-lock-lab-action@v1': ref: 'v1' commit: 'sha1-289fb71b6ad20503e1a75480232a9ea0277cd855' 結果 | # | 試したこと | 動いたコミット | 結果 | |---|---|---|---| | E2 | v1 を B に動かして、ロック済みのワークフローを実行 | A | 守られた。中の inner@v1 も A | | E2 | 同じリポジトリの再利用ワークフロー(./.github/workflows/…)経由 | A | 守られた | | E9 | v1 を A から辿れない別の履歴 C に動かして実行 | なし | エラーで止まった | | E3 | ロックファイルにない新しいワークフローで @v1 を使う | B | 素通り。警告もなし | | E8 | 別リポジトリの再利用ワークフロー …/remote.yml@v2 を、v2 を動かしてから呼ぶ | B | 素通り。ロックファイルにも載らない | | E4 | ロック済みのワークフローに、ロックしていないアクションを足す | なし | 起動できない(422) | | E5 | gh actions-lock --verify | なし | E3 と E4 を検出して終了コード 1。タグが動いたことは報告しない | | E6 | gh actions-lock(通常の再生成) | なし | 既存の行は A のまま。タグが動いたことは報告しない | | E7 | gh actions-lock --relock | なし | 「タグが動いた」と警告したうえで B に進め、終了コード 0 | タグを動かされても、ロックしたコミットが動いた(E2) v1 が B を指している状態でロック済みのワークフローを動かすと、ログの最初にこう出て、A がダウンロードされました。 Using locked action versions from the workflow's lockfile Download action repository 'OTA-EITA/actions-lock-lab-action@v1' (SHA:289fb71b6ad20503e1a75480232a9ea0277cd855) OUTER_MARKER=A INNER_MARKER=A 中で呼んでいる inner@v1 は、ロックファイルに別の行として載っていませんが、A のコミットのものが動きました。同じリポジトリの再利用ワークフローを経由しても同じです。 辿れない履歴に付け替えられると、止まった(E9) 攻撃者は、元のコミットの上に積むとは限りません。v1 を A と無関係の履歴 C に付け替えると、ロック済みのワークフローは次のエラーで止まりました。 ##[error]Lockfile pin 289fb71b6ad20503e1a75480232a9ea0277cd855 for `ota-eita/actions-lock-lab-action` does not match ref `v1`. Ensure the pinned commit exists on the expected branch or tag. ドキュメントには、ロックしたコミットは記録したタグやブランチから辿れる必要があり、辿れなくなったものは実行時に拒否されると書かれています。そのとおりの動きで、攻撃者のコードも元のコードも動かずに止まります。安全側ですが、上流がまっとうな理由で履歴を書き換えたときも、同じように止まります。 新しいワークフローは、ロックし直すまで守られない(E3) ロックファイルを作ったあとに、同じ @v1 を使うワークフローを 1 本足しました。ロックし直さずに動かすと、警告もなく B が動きました。 Download action repository 'OTA-EITA/actions-lock-lab-action@v1' (SHA:875663c7885e676528d7f888745932fec4d63a0e) OUTER_MARKER=B ロックファイルは「載っているワークフロー」にだけ効きます。ドキュメントにも、ワークフローは 1 本ずつでもまとめてでも onboard できる、とあります。載っていないワークフローは、これまでどおりタグで解決されます。 別リポジトリの再利用ワークフローは、ロックの外(E8) 別リポジトリの再利用ワークフローを jobs.<id>.uses で呼ぶワークフローをロックすると、そのワークフローの行は空([])になり、警告も出ませんでした。v2 を動かしてから実行すると、動かされた先がそのまま動きました。 Uses: OTA-EITA/actions-lock-lab-action/.github/workflows/remote.yml@refs/tags/v2 (6e74cf98180d2b9bba393e9fc1fb4493c5f9f040) REMOTE_MARKER=B ドキュメントにも、ロックファイルは再利用ワークフローの呼び出しを固定しないので、フルのコミット SHA で固定する、と書かれています。 --relock は、動いたタグに警告を出しつつ進める(E7) ロックを更新するための --relock を、v1 が B に動いた状態で実行しました。 warning ref-moved .github/workflows/use-action.yml ref v1 now resolves to 875663c7885e, lockfile pins 289fb71b6ad2 警告は出ますが、ロックファイルの v1 は B(875663c)に書き換わり、終了コードは 0 でした。README には「動いたピンを受け入れる」ための --accept-moved というフラグがありますが、付けなくても進みました。 更新を CI で定期的に回していると、タグを書き換えられた直後の --relock で、攻撃者のコミットをそのまま取り込むことになります。通常の再生成(E6)と --verify(E5)は、タグが動いたことを報告しませんでした。 ロックしていないアクションを足すと、起動できない(E4) ロック済みのワークフローに actions/setup-node@v6 を足して、ロックし直さずに動かそうとすると、起動の時点で弾かれました。 HTTP 422: Invalid Argument - failed to parse workflow: Invalid dependency lockfile ".github/workflows/actions.lock": workflow ".github/workflows/use-action.yml" references actions not present in the lockfile: actions/setup-node@v6 ほかのロック済みのワークフローは、この間も普通に動きました。 そのほかに分かったこと - gh actions-lock は、ロックファイルを作るだけでなく、ワークフローも書き換えます。actions/checkout@v7 は @v7.0.1 に、actions/upload-artifact@v7 は @v7.0.2 に変わりました。v1 のように semver でないタグは書き換えず、「完全な semver の参照を使う」よう info で勧めてきます。 - container: のイメージ(alpine:3)はロックされず、「digest で固定していない」と warning が出ます。 入れるときにやること - PR で gh actions-lock --verify を動かす。ロックファイルに載っていないワークフロー(E3)と、載っていないアクション(E4)を、マージ前に止められます - --relock の出力の ref-moved を確かめてから commit する。動いたタグは、上流のリリースノートやタグの履歴で、まっとうな更新か確かめます(E7) - 別リポジトリの再利用ワークフローはコミット SHA で固定する。ロックファイルでは守られません(E8) - コンテナのイメージは digest で固定する - ワークフローを足したらロックし直す。--verify を CI に入れておけば、忘れても止まります 確かめていないこと - Dependabot がアクションを更新するときに、タグが動いていたらどうなるか。Changelog には、Dependabot がロックファイルの行も同じ PR で作り直すとあります - Docker のアクション(docker:// や Dockerfile のアクション) - プライベートリポジトリのアクションと、GitHub Enterprise Server(gh actions-lock は対応していません) - ブランチ(@main など)で指定したアクションのロック 参考 - GitHub Actions workflow lockfiles are generally available(GitHub Changelog, 2026-10-28) - Workflow dependency lockfiles(GitHub Docs) - github/gh-actions-lock - GHSA-69fq-xp46-6x23: Trivy ecosystem supply chain was briefly compromised - GitHub Actions で checkout@v7 のようなバージョン指定を書くのはやめよう!時代は actions.lock!(Zenn) - OTA-EITA/actions-lock-lab、OTA-EITA/actions-lock-lab-action

  7. GitHub Universe2026現地レポート: Day1, Day2現地セッションレポート サンフランシスコで開催されたGitHub Universe 2026から、Day1, Day2で私が参加したセッションの内容をお届けします。 現地オンリーのセッションも含まれます。長大ですがどうぞお楽しみください。 ! 各セッションの内容は、私が現地で聞いた内容をベースに日本語にしています。 一部意訳も含みます。 Humans set direction. Agents close the loop. スピーカー: Mario Rodriguez, Chief Product Officer@GitHub Evan Boyle, Partner Director of Engineering@GitHub GitHubをエージェントネイティブなプラットフォームへ GitHubはエージェントネイティブなプラットフォームを目指している。エージェントによって創造性を高め、開発者がフロー状態を保てるようにすることが狙いだ。それが、我々が提唱するThe Agent-Native System Engineeringだ。 価値を測る基準は、使ったトークンの量ではない。エージェントによってどれだけのアウトカムを生み出せたかで測るべきである。 Copilot AppとCopilot Projects Copilot Appでは画像生成もできるようになった。また、KeynoteではCopilot Projectsが発表した。これにより、複数のリポジトリのコンテキストをエージェントに与え、プロジェクトにまたがる作業を進めることができるようになる。 Copilot Skills: 'Lever'でCIを改善する Copilot Skillsを使って「多くの人がやりたくない仕事」をエージェントに任せよう。その一例として、Copilot Skillsをフィードバックループの構築や安全な委任、繰り返し作業の自動化に活用するOSSの'Lever'を使ってCIの高速化の例を紹介しよう。 CIが遅いという課題に対し、まずはどこがボトルネックなのかを分析する。改善の目標や指標をコード変更前に定め、変更後の結果を比較できるようにする。PRをより早くGreenにすることをアウトカムとし、pushから必要なCIジョブがすべて成功するまでの時間を指標にしていた。CIにはノイズがあるため、同じコミットで比較実行し、効果が確認できた改善だけを残す。 Leverは、改善案をひとつずつ試すのではなく、複数のエージェントにアイデアを並列に検証させる。約20個の改善案を実際のCIで比較し、効果のない案は採用せず、結果が出たものだけを取り入れる進め方だ。 5日間で得られた成果 この取り組みでは、わずか5日間で次の成果が得られた。 135個の大きなcrateを分割し、サイズを約3分の1にした 20件のPull Requestのうち19件をマージした 遅いCIジョブのひとつを約28%高速化した 人間が入力したメッセージは38件。一方、スケジュールされたエージェントは373回実行された。人間が方向を定めたあと、エージェントが試行と検証のループを回す様子が数字にも表れている。 Leverの6つのコマンド Leverには、作業の目的に応じた6つのコマンドが用意されている。 loop:改善目標と評価指標を定義し、変更前後を比較する arms:複数のエージェントで改善案を並列に検証する refactor:巨大なコードを分割・整理する review:複数モデルでコードをレビューする shepherd:CIを維持しながら、変更のマージまで支援する brief:短い指示から、エージェントが自律実行できる計画を作る LeverはOSSとして公開されている。ソースコードはgithub/leverから確認できる。 エージェントに退屈な仕事を任せる 退屈な仕事はエージェントに任せ、人間は楽しい仕事に取り組もう。CIの改善のように、繰り返し仮説を試し、結果を確認する作業はエージェントが得意とする。一方で、人間は何を改善するのかを決め、成果を評価し、次の方向を示す。 また、新しいことに挑戦したら、ローカルホストの中だけで終わらせず、共有することも大切だ。創造性、コラボレーション、コミュニティ。これが我々が重視していることだ。これはLeverをOSSとして公開していることにもつながっている。 GitHub Copilot and Windows: Bringing local models into the workflow スピーカー:Ryan Hecht, Product Manager@GitHub Copilot FusionがKeynoteで紹介された。ここではWindowsのローカルモデルもそのCopilot Fusionのセッションで利用できるということを紹介する。 これにより、GitHubが提供している従来のクラウド上のモデルと、Windows上のローカルモデルを組み合わせて、Copilot Fusionがタスクの内容に応じて使い分けることができるようになる。 まず、OllamaでGemma 4、Nemotron 3.5、Qwen 3.8などのモデルをローカル環境に用意する。Copilotのモデル選択画面からローカルのOllamaエンドポイントを追加すると、Copilot Fusionのセッションで利用するモデルとして選択できる。 選択したモデルはそのセッションでのみ使われ、AutopilotやAssistantのセッション設定は変更しない。用途に応じてローカルモデルとGitHub Copilotのモデルを選び分けられる形だ。 ローカルモデルを既存のCopilotのモデル選択に組み込むことで、タスクや実行環境に合わせてモデルを選ぶ選択肢が広がるだろう。 GitHub Configuration as Code: Scaling org management with Terraform スピーカー:Yasuhiro Osaki, Software Engineer@SONY SONYではGitHub EnterpriseのOrgが増えるにつれ、管理のコストが増大した。管理者としてセキュリティリスクを鑑みて、自信を持って全てのユーザーに展開することができなかったため、新機能を無効にすることすらあった。セキュリティを優先すると、生産性を低下させるリスクもあった。 GitHubの管理モデルには、中央集権的な管理にするか、分散的な管理にするかという2つのアプローチがある。両者もメリットとデメリットがある。前者はガバナンスを明確にすることができる一方で、管理者への負担が集中し、管理はブラックボックス化するというデメリットがある。後者(分散的管理アプローチ)の場合、より自由に使うことができるが、GitHubの管理スキルに精通している必要になるし、ガバナンスは断片的になってしまう。 そこで取り組んだのが、Terraformを使って全てのGitHubの管理をコードとして管理することである。 それにより、全てのGitHub Enterprise 管理に関する変更は、PRを経てマージされ、透明性を持って管理される。 しかし全てのユーザーがTerraformを書く必要はない。ユーザーはissue templateからテンプレートを選択して変更リクエストを要求し、Copilot Cloud Agentをアサインする。その後、PR上でtf lint、terraform fmt、terraform validate、terraform plan、Copilot Code Reviewなどの自動チェックを経て、Terraformが更新される。チェックに通った変更はterraform applyでデプロイされる。 また、シークレットの管理も重要である。シークレットの管理にもTerraformを活用した。シークレットはハードコードをすべきではない。GitHub CLIとTerraformを活用し、GitHub Secretに登録されるようにした。 これにより、透明性と中央集権的管理を両立した。それにより以前よりも自信を持ってGitHubを管理することができるようになった。私のアドバイスは、全ての設定を一度にコード化しないことだ。 本番運用では、Membershipとリポジトリに関する変更リクエストが特に多かった。開発者が10人から150人以上に増えてもガバナンスを維持し、GitHub Copilotを使ってコードの作成やレビューも行っている。設定をオープンにすることで、オーナー、開発者、AIが一緒に変更に取り組めるようになった。3年間で、GitHubの設定を変更したPull Requestは810件になる。 大崎さんの登壇資料はこちらで公開されています! speakerdeck.com Resolve your security findings faster with GitHub Copilot スピーカー:Carlin Cherry, Senior Product Manager@GitHub / Jon Janego, Senior Product Manager@GitHub Dependabotは、サプライチェーンリスクに起因するセキュリティ脆弱性を発見してくれる。しかし、発見したあと、この大量のアラートをどうするか?ということが問題になる。ここでAgentic Autofixを活用することができる。エージェントにセキュリティインシデントへの対応を手伝ってもらうのだ。 Agentic AutofixはCloud Agentと同様、GitHub Actionsによって実行される。セッションログから、どのように脆弱性を修正したのかを追跡することができ、途中で追加の指示を追加することも可能だ。途中で追加した指示はMemoryに蓄積されるため、プロジェクト固有の振る舞いとして記憶した上で動く。 セキュリティリスクの修正にCopilotを使うようになる前と後では、修正のスピードが大幅に向上した。Code scanning alertの修正は45%、Dependabotのアラートの修正は75%修正速度が向上したという。 番外編: 会場のようす(1) ちなみにこのセッションはDemos & Donutsというスペースで行われていました。文字通りドーナツを食べながらデモを楽しめるスペースです。とてもアメリカっぽいドーナツが山盛りになっていました。 How Microsoft AI builds coding models optimized for GitHub Copilot スピーカー:Julia Kasper, Product Manager@Microsoft / Yanan Cai, Principal Product Manager@Microsoft Microsoftは先日のBuildからMAIというMicrosoft独自のモデルを発表した。Microsoftにとって独自モデルを作るベネフィットは、GitHub Copilotのリアルなワークフローに適合するようにチューニングした上で、1つのシステム上で動くモデルを設計できることだ。 ポイントは3つだ。 実際のAgentic Harnessやツールを使ってモデルを学習・評価する 品質、レイテンシ、トークン効率、推論コストを総合的に最適化する 開発者の利用データをもとにモデルと製品を継続改善する 単に高性能なモデルを採用するのではなく、開発体験全体を最適化するという思想が元になっている。 本番環境での実験前に、公開ベンチマークとGitHub独自の内部ベンチマークでオフライン評価を行う。タスクの成功率、Pass Rate、解決までの時間、推論の詳細度、レイテンシなどを測定する。単なるモデルの性能比較ではなく、実際の開発ワークフローでの品質と効率を重視している。 GitHub Copilotのモデル改善では、公開ベンチマークだけでなく、実際の製品環境でのA/Bテストを重視する。5月以降、1〜2週間ごとに新しいモデル候補を検証し、改善を続けている。 成果の例として、最初のトークンまでの時間を10%短縮、トークン間の待ち時間を25%短縮、エラー率を13%減少、途中キャンセル率を10%減少させたことが紹介された。 我々にとってのモデルの成功は、単なるベンチマークのスコアだけではない。いかにリアルな開発環境のワークフローに適合し、開発体験を向上させたかということを重要視している。 How GitHub Secures Open Source スピーカー:Bas Alberts, Principal Security Researcher@GitHub / Xavier René-Corail, Sr. Dir, Security Research@GitHub OSSは今や世界を動かしている。しかし、その安全を支えるメンテナーの負担は大きい。Log4jのメンテナーであるChristian Grobmeierは「誰もあなたの様子を確認しに来るわけではない。みんなが気にかけているのはプロジェクトの状況だ」という言葉を残している。これはOSSメンテナーが直面する現実を端的に表している。 しかしOSSの持続可能性を考えるうえで、我々GitHubはプロジェクトの健全性だけでなく、それを支える人間への配慮も重要であると考えている。 セキュリティを持続可能にするには、メンテナー個人にさらなる努力を求めるのではなく、負担を減らし、コミュニティ全体で支える仕組みが必要だ。そのための原則が次の3つだ。 Make it Easy:作業そのものを減らし、安全な方法をデフォルトにする Make it Delicious:普段の開発や学習の流れにセキュリティを組み込む Make it Social:活動時間に資金を提供し、専門知識と責任を共有する セキュリティの専門知識を再利用する 従来、攻撃者やセキュリティ専門家とOSSメンテナーの間には、専門知識へのアクセス格差があった。最先端のAIモデルによって、脆弱性を見つけるハードルは下がりつつある。一方で、見つけた脆弱性が実際に悪用可能かどうかを証明するには、依然として専門的な検証が必要だ。 そこで、専門家が得た知見を一度きりの対応で終わらせず、再利用できる形にしてOSS全体へ広げる。紹介された流れは、脆弱性をFindし、再利用できる検出パターンをExtractし、CodeQLクエリやLLMプロンプトとしてCodifyし、他のプロジェクトへScaleするというものだ。 GitHub Security LabのTaskflow Agentは、セキュリティ専門家の監査ノウハウを、再利用可能なプロンプトと構造化されたTaskflowに変換する。各OSSプロジェクトでエージェントを実行し、監査結果をレビューして再現性を検証することで、専門家の知見をプロジェクト間で活用できる。 発見だけでなく修正までつなげる 脆弱性を検出しても、修正につながらなければバックログが増えるだけだ。GitHub Copilot Autofixは、CodeQLが検出した脆弱性に対する修正案を生成する。GitHubが公開ベータ期間に収集したデータでは、修正をコミットするまでの時間の中央値が約3倍速くなったという。これは正しさを保証する数字ではなく、修正までの速度に関する結果だ。 リリース経路を守る リリース経路そのものが攻撃経路になることもある。2025年9月のShai-Hulud攻撃では、npmメンテナーのアカウントが侵害され、500以上の悪意あるパッケージが削除された。安全なリリース経路を標準化することが重要になる。 紹介された対策では、信頼できないコードにはリリース権限を与えず、保存されたトークンではなくワークフローに紐づくOIDCで公開する。また、段階的な公開プロセスを使い、安全なリリースを仕組みとして実現する。 GitHub CLIのgh secureを使うと、リポジトリのセキュリティ設定を確認し、適用を支援できる。たとえば、現在の状態を確認するにはgh secure status、変更を適用する前にプレビューするにはgh secure --dry-runを使う。Branch Protection、Secret Scanning、Dependabot、Code Scanningなどの設定を扱える。 GitHub Security Labによる脆弱性の責任ある開示の成果として、報告した脆弱性の95%が最終的に修正されている。脆弱性を発見・報告するだけでなく、実際の修正につなげることが重要。この数字は、OSSメンテナーとの信頼関係を表しており、一件一件の報告を通じて築かれきた。 OSSの安全をエコシステム全体で支える GitHub Secure Open Source Fundは、OSSメンテナーに資金、専門知識、セキュリティ支援を提供するプログラムだ。紹介された実績では、216のプロジェクトがセキュリティを改善し、4,000件以上の脆弱性が修正された。メンテナーの活動を支援することが、多くの利用者を守ることにつながる。 役に立つことを1つやれ。新たな作業の山を作ってはいけない。 OSSのセキュリティを個人の善意だけに依存させず、エコシステム全体で支えていく必要がある。 17 agents, one SDLC: How Levi Strauss reimagined development on GitHub スピーカー:Jenny Patchen, Sr. Director, Shared Platforms & Services@Levi Strauss & Co. Levi Strauss & Co.が、17のエージェントを活用してSDLCを改革した事例。最初に取り組んだのはエージェントを作ることではなかった。まず成功の定義を決め、品質を高め、あとから展開できるように設計と観測の方法を標準化した。 エージェントを作る前にワークフローを設計する 最初の数日は、品質エンジニアを含め開発プロセスに関わるエンジニアや各ロールのメンバーが集まり、数日かけてワークフローを設計した。繰り返すが、先にエージェントを作らず、まずプロセスに関わる人たちで仕事の流れを整理することが重要だ。 設計されたワークフローでは、人間がユーザーストーリーを定義し、回帰テストや品質を管理する。エージェントは実行を担い、判断は人間が行う。エージェントにすべてを任せるのではなく、人とエージェントそれぞれの役割を明確にしている。 たとえばRequirement Agentは、機能追加リクエストを受け取ると、依頼内容やチケット、仕様テンプレートをもとにテスト可能な要件を作成する。不明点があれば人に確認し、チーム全体が実装に使える明確な仕様を出力する。最後に人間が仕様を承認する。 17の専門エージェントを支える基盤 次に、エージェントを動かすためのプラットフォームを再構築した。開発者からのリクエストを1つのOrchestration Agentが振り分け、17の専門エージェントがそれぞれの役割を担う。GitHub.com、Copilot CLI、VS Codeなど開発者が使う場所から依頼でき、JiraやNew Relic、Playwrightなどのツールや情報とも連携する。 すべてのリポジトリは、最初からagent-readyな状態で作成される。スターターキットやCI/CD定義、エージェント向けの設定と指示を用意し、PRごとに依存関係やテスト、カバレッジなどのCIゲートを実行する。CIに失敗した場合はAgentic AutofixがCopilotに修正を依頼し、修正後に再検証する。 さらに社内マーケットプレイスを用意し、エンジニアがエージェントやプラグインをセルフサービスで導入できるようにした。登壇時点の紹介では、24のエージェント、19のスキル、15のMCPサーバーが登録され、利用するエンジニアが評価し、VS CodeやGitHubで共有できる仕組みになっている。 継続できる導入にする この取り組みを一時的な成功で終わらせず、継続可能なものにするため、AIエンジニアを含むチームを組織した。導入前の事前資料、実際のコードを使ったハンズオントレーニング、開発者向けのオフィスアワーなどを通じて、利用者のサポートにも取り組んだ。 運用上の原則も定めている。人間は判断を担い、曖昧さのある仕事に時間を使う。コンテキストはチャットではなく、リポジトリやチケットなどエージェントが確認できる場所に置く。エージェントにも人間と同じ品質ゲートを適用し、すべてのエージェントの動作と人間による上書きを記録して監査可能にする。 その結果、スループットは51%、PR合格率は54%向上し、AIの支援を受けて作成されたPRの割合は26%から69%に増加した。 導入から得られた学び 1つのブランチ上で複数の作業を並列に進める方法はうまくいかず、PR内では逐次実行する方が有効だった。また、要件の曖昧さは設計の早い段階で明らかになった。最初のワークショップには大きな需要があり、現在は展開を拡大している。次の開発チームは、より迅速に導入できる見込み。 重要なのは、エージェントの数を増やすことだけではない。タスクの分け方と、人間が判断しエージェントが実行する協働プロセスを設計することだ。1873年にワークウェアを再創造したLevi Straussが、今年再創造したのはソフトウェア開発だった。 Treat your AI context as infrastructure スピーカー:Christopher Harrison, Senior Developer Advocate@GitHub Copilotに一度指示するだけで素晴らしいアプリを作れる場面はある。しかし、エンタープライズの開発では、既存のアーキテクチャやテスト・ドキュメントのルール、過去の意思決定、タスク間の関係を考慮しなければならない。リポジトリには、アーキテクチャ、規約、タスクに関係する情報が蓄積されている。大きなリポジトリを丸ごと渡すのではなく、Copilotに必要な理解を与えることが重要だ。 仕事に合ったコンテキストとツールを選ぶ Copilot Code Reviewに多くの改善ツールを追加した結果、コストが上がり、有益な指摘が減った事例が紹介された。トレースを確認し、差分から始めて調査範囲を絞り、根拠を限定するように調整したところ、平均的な本番レビューコストは約20%低下した。同じプロンプトやツールの組み合わせが、異なる仕事でも同じ成果を生むとは限らない。目的に合ったツールを選び、結果を測る必要がある。 プロジェクト共通の規約や標準はinstructionsに書き、単発タスクへの依頼とは分ける。専門的なガイダンスは、対象ファイルに合わせてスコープを限定する。そうすることで、関係のないコンテキストを必要以上に読み込まずに済む。 繰り返し実行する手順はSkillsにまとめる。いつ使うかというトリガーを具体的にし、必要なチェックを実行させ、完了できなかった場合には証跡を残す。特定の役割に集中させたい場合はcustom agentを使い、利用可能なツールも必要なものに絞る。 HooksとMCP Serverも用途が異なる。Hooksはイベントをきっかけに処理を自動実行する仕組みであり、MCPは外部のツール、データ、サービスへの接続に使う。たとえば編集後にフォーマッターを実行する処理はHook、外部情報の取得やIssue作成、ブラウザー操作などはMCPが担う。 MCP Serverに接続できることは、その操作が認可されていることを意味しない。アクセス権限、承認、ポリシーを適用する必要がある。また、SkillとMCPはどちらか一方で置き換えるものではない。手順をSkillに書く一方で、外部データへの効率的なアクセスや操作にはMCPを使う。 組織のコンテキストをエージェントに届ける たとえば本番環境で障害が起きたとき、コードはリポジトリにあっても、実際に稼働しているリビジョン、サービスオーナー、Runbookの情報は別の場所にある。Custom MCP Serverを使って、サービスカタログや本番デプロイ情報を読み取り専用でCopilotに提供すれば、開発者は別々のポータルを探し回らずに調査を始められる。 複数のエージェントで1つのタスクに取り組む例も紹介された。Feature Build Agentが変更を実装し、QA Agentが動作や回帰を検証し、Accessibility Specialistがラベルやフォーカス、キーボード操作を確認する。それぞれが適切なInstructions、Skills、認可されたツールを使い、調査結果と根拠を返す。作業範囲を限定し、成果を証拠とともに返し、タスク全体のオーナーは一人に保つ。 イベントやスケジュールをトリガーにした共有Automationを使えば、チームのコンテキストをもとに反復可能な作業を行い、レビュー可能な成果物を残せる。トリガー、使うツール、プロンプトはチームが管理する。 一方、MCPやプラグインを組織で安全に使うにはガバナンスが欠かせない。Enterprise managed settingsを使えば、利用を許可するMCP Serverを制御し、プラグインの承認やブロック、インストール元の制限などを組織レベルで適用できる。 コンテキストを育て、評価する Copilotはツールにすぎず、ツールを渡すだけでは定着しない。教育に投資し、利用している開発者と話し、どこで失敗するかを知ることが重要だ。テレメトリーや評価だけでは分からない摩擦を、アンケートや会話から把握し、共有コンテキストの改善につなげる。 GitHub CopilotのランタイムをTypeScript/Node.jsからRustへ移行した事例では、CodingやReviewのSkillを整備して再利用した。さらにEvalsで期待する結果を定義し、検証、実行、評価を繰り返した。コードを生成する仕組みそのものを改善し、失敗から学んでInstructionsや評価ケースを強化する考え方だ。 最初から大規模な仕組みを作る必要はない。まずは一つのリポジトリと、繰り返し発生するタスクや摩擦を選ぶ。Copilotに既存のコードやドキュメントをもとにInstructionsの案を作らせ、レビューして実際の作業で試す。有効なガイダンスにはオーナーを割り当て、チームのフィードバックを受けて改善する。必要になった段階でSkillsや専門エージェント、ツールを加えていく。 番外編:会場のようす(2) 会場には「Wellness Lounge」というリラックスルームがありました。セッションの合間にゆっくり休憩できる、マインドフルな雰囲気のスペースです。薄暗い空間に水のせせらぎの音が響いていて、とても落ち着きました。 会場は海のすぐそばにあり、建物の近くにはカモメが来ていました。日本で見かけるカモメより少し大きめで、もふもふしていてかわいかったです。

  8. GitHub MCPサーバーとblastengine MCPで、リリースノートから利用者向けのお知らせメールを作って一斉配信する GitHubのリリースノートは、開発者が開発者に向けて書くものです。「Fix all Dependabot alerts」「bump version」のような書き方では、ツールを使っているだけの利用者には何をすればいいのかが伝わりません。リリースのたびに、利用者向けにお知らせを書き直して送る作業が発生します。 GitHubの公式MCPサーバーとblastengineのMCPサーバーを1つのエージェントにつなぐと、この作業を会話で回せます。リリースノートと関連するプルリクエストを読ませて利用者向けの文面に直し、blastengineの一斉配信の下書きにします。人が内容を確かめてから確定する流れです。本記事ではこの構成をClaude Codeで組み、実際に1通送るところまで確認します。 題材には、blastengine MCPサーバー自身のリリース(v0.2.0〜v0.2.2)を使います。社内でblastengine MCPを配っている担当者が、営業やCSを含む利用者に更新をお願いする、という場面です。 ※記事中の会社名と送信元メールアドレスは、掲載用に置き換えています。 blastengine MCPの導入手順は導入記事、各ツールの入出力は全15ツールの検証記事を参照してください。 全体構成 ┌──────────────────────────────┐ │ Claude Code │ └───────┬──────────────┬───────┘ stdio │ │ stdio (Docker) │ │ ┌────────────────▼───┐ ┌───▼──────────────────┐ │ GitHub MCP │ │ blastengine MCP │ │ (読み取り専用) │ │ (一斉配信を有効化)│ └────────┬───────────┘ └───────────┬──────────┘ │ GitHub API │ ┌────────▼───────────┐ ┌───────────▼──────────┐ │ GitHub │ │ blastengine │ └────────────────────┘ └──────────────────────┘ GitHub MCPサーバーは公式のDockerイメージ(ghcr.io/github/github-mcp-server)を使い、stdioで動かします。今回はリリースとプルリクエストを読むだけなので、読み取り専用で起動します。 準備1:GitHubのトークンを用意する GitHub MCPサーバーは、環境変数GITHUB_PERSONAL_ACCESS_TOKENに渡したトークンでGitHub APIを呼びます。 お知らせを作るのに必要なのは、リリースとプルリクエストを読む権限だけです。普段使っているrepoスコープ付きのトークンを流用せず、対象のリポジトリだけを読めるfine-grained personal access tokenを発行して渡すのが安全です。 準備2:Claude Codeに2つのMCPを登録する { "mcpServers": { "github": { "type": "stdio", "command": "docker", "args": ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server:latest", "stdio", "--read-only", "--tools=list_releases,get_release_by_tag,pull_request_read"], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" } }, "blastengine": { "type": "stdio", "command": "node", "args": ["/absolute/path/to/blastengine-mcp/dist/index.js"], "env": { "BLASTENGINE_LOGIN_ID": "${BLASTENGINE_LOGIN_ID}", "BLASTENGINE_API_KEY": "${BLASTENGINE_API_KEY}", "BLASTENGINE_ENABLE_SEND": "false", "BLASTENGINE_ENABLE_BULK": "true", "BLASTENGINE_ENABLE_CSV_IMPORT": "false" } } } } GitHub側はイメージ名の後ろにstdioサブコマンドと起動オプションを並べます。--read-onlyで書き込み系のツールを外し、--toolsで公開するツールを3つに絞っています。 blastengine側は、[kintoneの記事]((kintone記事のURL))や[スプレッドシートの記事]((Sheets記事のURL))とは逆に、一斉配信(BLASTENGINE_ENABLE_BULK)だけを有効にしています。お知らせは全員に同じ文面を送るので、宛先ごとに1件ずつ送るトランザクション配信より、一斉配信が向いています。 GitHub MCPのツールを絞る GitHub MCPサーバー2.0.3は、何も指定しないとcontext、copilot、issues、pull_requests、repos、usersのツール群が有効になります。手元では46個のツールが公開され、その中にはファイルの作成や削除、プルリクエストのマージといった書き込み系も含まれていました。 起動オプションを変えてtools/listを取り、claude -pに「OKとだけ返して」と指示したときの入力トークン数も比べました。 | 起動オプション | ツール数 | 入力トークン | MCPなしとの差 | | --- | --- | --- | --- | | MCPサーバーなし | ― | 5,462 | ― | | 指定なし(既定) | 46 | 25,803 | +20,341 | | --read-only --toolsets=repos,pull_requests | 16 | 13,689 | +8,227 | | --read-only --tools=list_releases,get_release_by_tag,pull_request_read | 3 | 7,672 | +2,210 | 既定のままだと、ツール定義だけで2万トークンを超えます。読み取り専用にしてツール群を2つに絞ると約8,200トークン、使うツールだけを名指しすると約2,200トークンで、既定の約1割になりました。 --toolsetsはツール群の単位で、--toolsは個々のツールの単位で指定します。--read-onlyと組み合わせると、読み取り系のツールだけが残ります。 --allowedToolsではコンテキストは減らない 検証の途中で、kintoneやスプレッドシートのMCPサーバーも登録したままの設定で、同じ指示を実行してしまった回がありました。Claude Codeの--allowedToolsではGitHubの3ツールだけを許可していたのですが、結果は5ターンで約0.43ドルでした。GitHubとblastengineだけを登録した設定で実行し直すと、6ターンで約0.13ドルです。 --allowedToolsやpermissionsは、ツールの実行を許可するか止めるかを決める仕組みです。登録されたMCPサーバーのツール定義は、許可していないものも含めてすべてエージェントに渡ります。コンテキストを減らしたいなら、使わないサーバーは設定ファイルから外し、使うサーバーはサーバー側のオプションでツールを絞る必要があります。 手順1:リリースノートとプルリクエストを読ませる あなたは社内で blastengine MCP サーバー(GitHub: rakus-lc/blastengine-mcp)を配布・管理している担当者です。 社内の利用者(エンジニアではない営業・CSのメンバーも含む)に、アップデートのお知らせメールを書きます。 1. GitHub の rakus-lc/blastengine-mcp のリリース一覧を取得し、v0.2.0 より後に出たリリース(v0.2.1, v0.2.2)の本文を読んでください。 2. 本文の「What's Changed」に出てくるプルリクエストは、必要に応じて内容を読み、何が変わったのかを確認してください。 エージェントはlist_releasesを1回呼び、続けてpull_request_readをプルリクエスト#2〜#5の4件分呼びました。 list_releasesの返り値には、各リリースの本文がそのまま含まれていました。そのため、タグを指定して1件ずつ取るget_release_by_tagは使われていません。リリースの数が少ないリポジトリなら、list_releasesとpull_request_readの2つで足ります。 リリースノートの「What's Changed」は、プルリクエストのタイトルを並べただけのものです。v0.2.2の場合は次の1行でした。 * Fix all Dependabot alerts and bump version to 0.2.2 by @rakus-shota-yamada in https://github.com/rakus-lc/blastengine-mcp/pull/5 これだけでは、利用者に影響があるのかどうか判断できません。プルリクエスト#5の本文には、解消したアラートの件数と深刻度、変更したファイルがpackage.jsonとpackage-lock.jsonの2つだけであることが書かれていました。エージェントはこれを読んで、「機能の追加や使い方の変更はない」と判断しています。 また、v0.2.0とv0.2.1のリリース本文の冒頭には、成果物を取り下げたという警告が入っていました。エージェントはこの警告を拾い、「v0.2.0とv0.2.1を使っている人は更新が必要」というお知らせの軸を組み立てています。 手順2:利用者向けの下書きを作らせる 3. 利用者向けのお知らせメールの件名と本文(テキスト)の下書きを作ってください。 - 利用者がやるべきこと(更新が必要か、どの版を使えばよいか)を最初に書く - 誤字修正やメタデータの変更など、利用者に影響しない変更は載せないか、まとめて一言にする - リリースノートとプルリクエストに書かれていないことは書かない。推測で補った箇所や、ある時点での状態を述べている箇所があれば、下書きの後に別に列挙する - 送信元・署名は「株式会社example 情報システム担当」とする メールはまだ送らないでください。下書きと、読んだリリース・プルリクエストの一覧を報告してください。 返ってきた下書きの本文は次のとおりです。 各位 株式会社example 情報システム担当です。 blastengine MCP サーバーの新しい版(v0.2.2)が公開されました。 ■ お願いしたいこと v0.2.0 または v0.2.1 をお使いの方は、v0.2.2 への更新をお願いします。 v0.2.0 と v0.2.1 の配布ファイルは、同梱していた部品(依存パッケージ)に 既知の脆弱性が含まれていたため、公開元で取り下げられています。 今後は v0.2.2 以降をご利用ください。 ■ 更新方法 配布ページから v0.2.2 のファイル(blastengine-mcp-server-0.2.2.mcpb)を ダウンロードし、README のインストール手順に従って入れ替えてください。 (配布ページと README のURL) ■ 変更内容 ・v0.2.1: 同梱部品の脆弱性(15件)を修正しました。 ・v0.2.2: 同梱部品の脆弱性(11件)を追加で修正しました。 ・そのほか、README の表記修正など、使い方に影響しない軽微な変更が含まれます。 (以下略) 利用者がやるべきことが冒頭にあり、誤字の修正やマニフェストの変更は「軽微な変更」の一言にまとめられています。指示どおりの形です。 「その時点の状態」を言い切らせない 指示の3つ目にある「ある時点での状態を述べている箇所」の一文は、最初の指示にはありませんでした。その一文がないときの下書きには、次の文が入っていました。 v0.2.2 の時点で、既知の脆弱性は残っていません。 出どころは、プルリクエスト#5の本文にある「残存する脆弱性: なし(npm auditは0件)」という記載です。でたらめではありません。ただ、これはプルリクエストをマージした時点の話です。お知らせを送る日には、新しい脆弱性が見つかっているかもしれません。利用者が読めば「v0.2.2は安全」と受け取ります。 しかも、このときの下書きの後には、推測で補った箇所が8つ列挙されていたのに、この文はその中に入っていませんでした。資料に書いてあることなので、エージェントにとっては推測ではないからです。 指示に一文を足して実行し直すと、この文は本文から消え、下書きの後に次のように列挙されました。 ある時点の状態を述べている箇所 - 「15件」「11件」は、各 PR 作成時点の Dependabot アラート件数です。 - PR には「残存する脆弱性なし」「npm audit 0件」とありますが、これもマージ時点の話です。 そのため本文では「現在は安全」とは書いていません。 - PR #4 と #5 の脆弱性の一部は開発用の依存でした(tmp、vitest など)。 バンドルに入る本番依存かどうかは項目ごとに違います。 本文では「同梱部品」とまとめましたが、厳密には全件が配布物に入っていたわけではありません。 リリースノートやプルリクエストは、書かれた時点の事実を書いたものです。それを後日のお知らせに転記すると、時点がずれた主張になりやすくなります。資料にない推測を挙げさせるだけでは足りず、時点のある記述も挙げさせておくと、人が確認すべき箇所がはっきりします。 3つ目の項目のとおり、件数を「同梱部品の脆弱性」とまとめた表現は、厳密ではありません。エージェント自身がそう申告しているので、人が確認する段階で「依存パッケージの脆弱性(15件、開発用を含む)」に直しました。 手順3:一斉配信の下書きを作る 直した本文で、blastengineの一斉配信の下書きを作らせます。この段階では、確定(送信)のツールを許可しません。 blastengine で、社内利用者向けのお知らせメールを一斉配信の下書きとして用意します。確定(送信)はしないでください。 1. まず blastengine_deliveries_list で、件名に「v0.2.2」を含む配信がすでにないか確認してください。 送信済みや予約済みのものがあれば、下書きは作らずにそれを報告して終了してください。 2. なければ blastengine_bulk_begin で下書きを作ってください。(送信元・件名・本文を指定) 3. blastengine_bulk_update_recipients で宛先を設定してください。 4. blastengine_bulk_preview で下書きの内容を取得し、指示どおりかを確認して報告してください。 claude -p "(上の指示)" \ --mcp-config .mcp.json --strict-mcp-config --tools "" \ --allowedTools "mcp__blastengine__blastengine_deliveries_list,mcp__blastengine__blastengine_bulk_begin,mcp__blastengine__blastengine_bulk_update_recipients,mcp__blastengine__blastengine_bulk_preview" エージェントは次の順でツールを呼びました。 1. blastengine_deliveries_listを{"subject": "v0.2.2", "delivery_type": ["BULK"]}で呼ぶ。結果は0件 2. blastengine_bulk_beginで下書きを作る(返り値は{"delivery_id":12}) 3. blastengine_bulk_update_recipientsで宛先を設定する 4. blastengine_bulk_previewで内容を確認する ここまでで5ターン、約0.09ドルでした。 bulk_previewでは本文の全文を確認できない blastengine_bulk_previewの返り値は次のとおりでした。 { "delivery_id": 12, "subject": "【要対応】blastengine MCP を v0.2.2 に更新してください", "from": { "email": "info@example.com", "name": "株式会社example 情報システム担当" }, "recipient_count": 1, "body_summary": "各位 株式会社example 情報システム担当です。 blastengine MCP サーバーの新しい版(v0.2.2)が公開されました。 ■ お願いしたいこと v0.2.0 または v0.2.1 をお使いの方は、v0.2.2 への更新をお願いします。...", "note": "This preview is informational only. It does not authorize or block commit. Bulk commit tools execute directly when BLASTENGINE_ENABLE_BULK=true." } 返ってくるのは件名、送信元、宛先数と、改行を詰めて冒頭だけを切り出したbody_summaryです。配信の状態(下書きかどうか)も本文の全文も含まれません。noteにあるとおり、このツールは確認用の情報を返すだけで、確定を止める働きもありません。 エージェントは、本文が指示と1文字ずつ一致するかと状態については「確認できていない」と報告し、管理画面での確認を勧めてきました。分からないことを分かったように報告しなかったのは良い挙動です。 人が全文を確かめたい場合は、blastengine_delivery_getも許可しておきます。配信情報にはstatus(下書きならEDIT)とtext_part(本文の全文)が含まれます。今回はAPIを直接呼んで、配信ID 12がBULK・EDIT・宛先1件で、本文が指示どおりであることを確認しました。 手順4:人が確認してから確定する 下書きの内容を人が確かめたら、確定のツールだけを許可して実行します。 claude -p "一斉配信の下書き(配信ID 12)を blastengine_bulk_commit_immediate で即時に確定してください。ほかの配信IDには触れないでください。確定後、blastengine_delivery_get と blastengine_mail_results_list で配信ID 12 の状態と宛先ごとの配信結果を確認してください。配信ログが空なら、少し待って引き直してください。" \ --mcp-config .mcp.json --strict-mcp-config --tools "" \ --allowedTools "mcp__blastengine__blastengine_bulk_commit_immediate,mcp__blastengine__blastengine_delivery_get,mcp__blastengine__blastengine_mail_results_list" 下書きを作る実行と確定する実行を分け、それぞれで許可するツールを変えています。こうすると、下書きを作る段階で誤って送ってしまうことはありません。また、確定する段階では配信IDを人が指定するので、確定する配信を取り違えにくくなります。 送信の直前に承認を挟む方法は、ほかに誤送信を防ぐ記事で扱ったhooksもあります。 一斉配信でも確定直後は結果が出ていない 確定の直後にblastengine_delivery_getを呼ぶと、statusはSENTなのにsent_countは0でした。同時に引いたblastengine_mail_results_listも空です。 { "delivery_id": 12, "delivery_type": "BULK", "status": "SENT", "total_count": 1, "sent_count": 0, "drop_count": 0 } エージェントが引き直すと、配信ログにSENT / 250が現れ、sent_countも1になりました。配信ログの配信時刻が00:44:44、更新時刻が00:44:47で、確定から数秒のずれです。 kintoneの記事とスプレッドシートの記事ではトランザクション配信で同じ現象が起きていましたが、一斉配信でも同様でした。配信情報のstatusのSENTは、配信処理の段階を表すものです。届いたかどうかは、sent_countや配信ログで確かめます。 確定から確認までは6ターン、約0.04ドルでした。 同じリリースを二重に案内しない 手順3の最初に入れた重複チェックは、もう一度同じ指示を実行したときに効きます。確定した後に、重複チェックの部分だけを実行し直しました。 件名に「v0.2.2」を含む一斉配信(BULK)が1件ありました。配信済みなので、新しい下書きは作っていません。 blastengine_deliveries_listのsubjectで件名を部分一致で探しているので、件名にバージョン番号を入れておけば、そのリリースを案内済みかどうかを判定できます。 件名だけで判定すると、作ったまま放置した下書きや、DROPばかりで届いていない配信も「案内済み」に数えてしまう可能性があります。指示では、見つかった配信のstatusやsent_countまで見て判断させるようにします。 なお、blastengineの配信情報の保持期間は配信開始から62日です。それより前のリリースの案内は、この方法では判定できません。 つまずきやすいポイント | 症状 | 原因と対処 | | --- | --- | | 何もしないうちにコンテキストを2万トークン使っている | GitHub MCPの既定では46ツールが公開される。--read-onlyと--tools(または--toolsets)で絞る | | --allowedToolsで絞ったのにコストが下がらない | --allowedToolsは実行の許可だけで、ツール定義は登録したサーバーの分がすべて渡る。使わないサーバーは設定から外す | | お知らせに「現在は安全」のような断定が入る | プルリクエストやリリースノートに書かれた、その時点の状態を転記している。時点のある記述は別に列挙させ、人が確認する | | リリースノートだけでは利用者への影響が分からない | 「What's Changed」はプルリクエストのタイトルの一覧。pull_request_readで本文を読ませる | | bulk_previewで本文の全文を確認できない | 返るのは冒頭のbody_summaryだけで、状態も含まれない。全文はblastengine_delivery_getのtext_partで確認する | | 確定直後にstatusがSENTなのにsent_countが0 | 配信ログの反映待ち。少し待ってblastengine_mail_results_listを引き直す | | 案内済みかどうかの判定がずれる | 件名だけでなく、見つかった配信のstatusやsent_countまで見て判定させる | | 下書きの段階で送ってしまいそう | 下書きを作る実行と確定する実行を分け、確定のツールは確定する実行でだけ許可する | まとめ GitHubとblastengineを1つのエージェントにつなぐ構成のポイントは3つです。 - GitHub MCPサーバーは--read-onlyと--toolsで、使う読み取り系のツールだけを公開する。ツール定義のコンテキストは既定の約2万トークンから約2,200トークンに減る - リリースノートだけでなくプルリクエストまで読ませると、利用者への影響を判断した文面になる。ただし、資料に書かれた「その時点の状態」は言い切らせず、別に列挙させて人が確認する - 一斉配信は、下書きを作る実行と確定する実行を分ける。bulk_previewは要約しか返さないので、全文の確認にはblastengine_delivery_getを使う 開発者向けの記録から利用者向けのお知らせへの書き換えはエージェントに任せ、何を言い切ってよいかの判断と送信の確定は人が持つ、という分担が、この構成の使いどころです。 --- blastengine MCPサーバーへの要望や感想は、以下のアンケートフォームから送ると開発チームに届くみたいです。 <docs.google.com> ※本記事の手順は、GitHub MCPサーバー2.0.3(Dockerイメージ)とblastengine MCPサーバー v0.2.2を接続し、Claude Code(Sonnet)からリリースとプルリクエストの読み取り、下書きの作成、一斉配信の下書き作成、確定、配信結果の確認まで通しで確認しました。提供ツールや引数はバージョンによって変わるため、手元のtools/listを確認してください。

    1 like
  9. Bamboo Weekly #194: GitHub (solutions) Bamboo Weekly Bamboo Weekly #194: GitHub (solutions) By Reuven M. Lerner • 29 Oct 2026 Last chance to join PythonDAB! The 10th cohort of my Python, Data, and AI bootcamp (PythonDAB) starts on November 5th. If you want to get fluent with these technologies in a small, collaborative cohort that emphasizes discussion, interaction, and hands-on practice, then this might be the program for you. Get more information at lernerpython.com, or contact me at reuven@LernerPython.com. How much do people use GitHub? I had these thoughts in the last few weeks, when Anthropic (the soon-to-be-public AI company behind Claude) told the world that they were no longer using Git for version control, because it couldn't handle the number of commits they were now creating with agentic programming. The thing is, Git isn't just a version-control system any more. It's also a way for people to collaborate. For most Git users, this means using GitHub (or one of its smaller competitors, such as GitLab or Bitbucket), which allows you to share your Git repository, and all its commits with others — sometimes in your organization, and sometimes with the entire world. It's up to you. This week, we'll look at data about GitHub: How many people use it? How many pushes do we see in a given year? How many repositories are there? What countries produce the most pushes? And have we seen any serious changes in the last year, since agentic coding took over the industry? Data and five questions This week's data comes from GitHub itself – and more specifically, from what it calls the "GitHub Innovation Graph," with data (of course) hosted on GitHub at github/innovationgraph . This data reflects public (i.e., largely open-source) activity on GitHub, aggregated and published every quarter since 2020. The data comes as a set of eight CSV files, each describing a different facet -- pushes, repositories, and licenses, among others. Paid subscribers, both to Bamboo Weekly and to my LernerPython+data membership program (lernerpython.com) get all of the questions and answers, as well as downloadable data files, downloadable versions of my notebooks, one-click access to my notebooks, and invitations to monthly office hours. Learning goals for this week include: Reading multiple files, joins, grouping, pivot tables, window functions, and plotting with Plotly. Here are my five solutions and explanations for this week's questions: Clone the repository from GitHub. Read all eight files into a dictionary, in which the filenames are the keys and the data frames (from reading the CSV files) are the values. Create a line plot showing, in total, the number of pushes that GitHub has seen per year. How do you explain the numbers for 2026? I started off by importing Pandas, Plotly, and also pathlib, a module in the standard library that is perfect for these sorts of situations: import pandas as pd from plotly import express as px from pathlib import Path Next, in order to create a dictionary of data frames, I used a dict comprehension. I started off by creating a Path object on the directory, and invoking glob on it. This returned a list of files in the data directory matching the '*.csv' pattern. I then iterated over these filenames, one at a time, creating a key-value pair: - To get the key, I first retrieved one_filename.name, giving me the file's name, without the leading path. I then invoked str.removesuffix on that string to remove the '.csv' suffix. - To get the value, I invoked pd.read_csv on one_filename. That returned a data frame – and while that might seem like a weird way to store data frames, it's actually quite convenient when you have a number of them and don't want to use multiple variables. The code is thus: dirname = 'data/bw-194/' all_dfs = {one_filename.name.removesuffix('.csv') : pd.read_csv(one_filename) for one_filename in Path(dirname).glob('*.csv')} That resulted in a dict with eight key-value pairs. To get the total number of pushes in GitHub from each year, I queried the all_dfs['git_pushes'] data frame. I then used groupby and the sum aggregation method to find the total number of pushes per year. I then used pipe and px.line to get a line plot: ( all_dfs['git_pushes'] .groupby('year')['git_pushes'].sum() .pipe(px.line) ) The resulting plot: We can see a dramatic increase in the number of pushes to GitHub over the years. It hit about 1 billion total pushes in 2025, when the number in 2024 was about 0.75 billion. So we see lots of growth, right? Well... we did, but we don't see it in 2026. What's going on? The main problem here, in my opinion, is that we're looking at the wrong level of granularity. The data includes not just a year column, but also a quarter column. I decided to do a second query, creating a new year_quarter column (with assign) that contains both, and then plotting with that: ( all_dfs['git_pushes'] .assign(year_quarter = pd.col('year').astype(str) + '-' + pd.col('quarter').astype(str)) .groupby('year_quarter')['git_pushes'].sum() .pipe(px.line) ) The result: That makes a lot more sense, no? moreover, we can see that the number of pushes has absolutely skyrocketed since the start of 2025, roughly when we saw people start to use AI for coding in a serious way. What 10 economies (i.e., countries plus territories that aren't quite countries) have the greatest number of repositories in 2026? Using data from datasets/population, which 10 countries have the greatest number of repositories per capita in 2026? I retrieved the repositories data frame from my dictionary, and then used loc andpd.col to keep only the rows in which year was 2026. Then I used groupby on iso2_code (the two-letter country abbreviation) to get the total number of repositories for each country in each year. Finally, I invoked nlargest(10) to get the 10 countries with the greatest number of repos: ( all_dfs['repositories'] .loc[pd.col('year') == 2026] .groupby('iso2_code')['repositories'].sum() .nlargest(10) ) The result: iso2_code repositories US 77284974 IN 69251720 EU 56672501 BR 19918250 CN 18424702 JP 11846724 GB 11018799 HK 10849015 ID 10517820 DE 10506693 This isn't a huge surprise: The US is in first place, followed by India, followed by the EU (which fits the definition of an "economy," even if it isn't a country). Then we have Brazil, China, Japan, the UK, and Hong Kong, with Indonesia and Germany taking the last two slots. Population obviously plays a role here; a country with more people will likely have more repositories. That's why I thought it would be interesting to get the per-capita number of repositories. A country in which every person had one GitHub repo would have a per-capita number of one (and a very nerdy population, at that!). I suggested using a population data set from GitHub. I read it into Pandas in this way: population_url = 'raw.githubusercontent.com' population_df = ( pd .read_csv(population_url) .pivot_table(index='Country Code', columns='Year', values='Value') [2025] .reset_index() ) In other words, I retrieved the data, and immediately invoked pivot_table to put countries in the rows and years in the columns. I then grabbed the column for the 2025 year, and also invoked reset_index, giving me a two-column data frame. It would be great to join the earlier data with this, but that won't be possible – because this population data set uses three-letter country abbreviations, and the GitHub data uses two-letter country abbreviations. I thus downloaded another data set from GitHub, one which has both abbreviations. I invoked p.read_csv to retrieve it from GitHub, and passed the usecols keyword argument to keep only the country name and the two abbreviations: country_url = 'raw.githubusercontent.com' country_codes = pd.read_csv(country_url, usecols=['name', 'alpha-2', 'alpha-3']) I then went about combining these data frames. I did so using merge, because it allows us to perform the combination using any columns. This is in contrast with join, which only works on the index. I started the query by retrieving all_dfs['repositories'], and then keeping only those lines from 2026. I then (again) performed the groupby on iso2_code, getting the total number of repos for each economy. Next, I used reset_index to move the two-letter code back into the data frame: ( all_dfs['repositories'] .loc[pd.col('year') == 2026] .groupby('iso2_code')['repositories'].sum() .reset_index() ) Next, I invoked merge twice, once between the repositories data frame and the country_codes data frame, and a second time between the result of the first merge and population_df. country_codes acted as a translation table, since it contained both of the types of codes. ( all_dfs['repositories'] .loc[pd.col('year') == 2026] .groupby('iso2_code')['repositories'].sum() .reset_index() .merge(country_codes, left_on='iso2_code', right_on='alpha-2') .merge(population_df, left_on='alpha-3', right_on='Country Code') ) I then went about cleaning things up: I moved the name (i.e., country name) column to the index with set_index. I then used assign to create a repos_per_person column, the result of dividing repositories by pd.col(2025), the population we have from last year. I then used [] to get just the result column, invoked round to get reasonable (if approximate) numbers, and asked for the 10 largest per-capita GitHub countries: ( all_dfs['repositories'] .loc[pd.col('year') == 2026] .groupby('iso2_code')['repositories'].sum() .reset_index() .merge(country_codes, left_on='iso2_code', right_on='alpha-2') .merge(population_df, left_on='alpha-3', right_on='Country Code') .set_index('name') .assign(repos_per_person = pd.col('repositories') / pd.col(2025)) ['repos_per_person'] .round(2) .nlargest(10) ) The result: name repos_per_person Hong Kong 1.45 Singapore 1.19 Seychelles 0.55 Estonia 0.32 Netherlands, Kingdom of the 0.27 Iceland 0.25 Andorra 0.24 Canada 0.24 Finland 0.24 Ireland 0.24 Hong Kong and Singapore each has more than one GitHub repo per person. Wow! We then get to some smaller countries, where a small pouplation helps boost their rating – although the Netherlands, Canada, Finland, and Ireland aren't in the same league as, for example, Estonia or Iceland. Upgrade to continue reading. Become a paid member of Bamboo Weekly to get access to all premium content. Upgrade More like this Less like this Keep reading Bamboo Weekly #194: GitHub Get better at: Excel files, plotting with Plotly, Marimo widgets, window functions, and sorting. Bamboo Weekly #193: World economic outlook (solutions) Get better at: Excel files, plotting with Plotly, Marimo widgets, window functions, and sorting. Bamboo Weekly #193: World economic outlook (update) Get better at: Excel files, plotting with Plotly, Marimo widgets, window functions, and sorting. Subscription details You are receiving this because you are a free subscriber to Bamboo Weekly. Email: [subscriber] Member since: 18 October 2026 Want Python itself, not just Pandas? I also write Better Developers, a free Monday newsletter about how the language works.

  10. Reddit · · r/openagi · u/syedshad · github/copilot-sdk

    GitHub’s multi-model Copilot harness comes to Microsoft Foundry for building custom AI agents Microsoft has introduced the GitHub-built Copilot harness in Foundry Agent Service in public preview. Developers can supply their own instructions, skills and tools while Foundry manages the agent loop and execution environment. The open-source connection is the Copilot SDK, which is available under the MIT license. It lets developers integrate Copilot’s agent capabilities into their own applications, with SDKs for Python, TypeScript, Go, .NET, Java and Rust. There is a licensing distinction: the SDK communicates with Copilot CLI, which has a separate license restricting modification of the CLI itself. The SDK’s MIT license does not make the entire agent runtime open source. The Foundry preview also supports Model Context Protocol (MCP) tools, reusable skills and agent definitions maintained alongside application code. These provide ways to customize and connect agents while the execution infrastructure remains managed by Foundry. sources - Foundry public preview announcement - Copilot SDK documentation and architecture - Copilot SDK MIT license - Copilot CLI license

    4 points · 2 comments
Trending awards, every completed day and week
Gold
801st place finishes
Silver
1402nd place finishes
Bronze
2113rd place finishes
Awarded repositories
34Owned by github now

Repositories

Ordered by gold, then silver, then bronze.

  1. docsBest: 1st by forks, 662 forks2398141
  2. hubotBest: 1st by stars, 1,005 stars1024
  3. spec-kitBest: 1st by stars, 5,685 stars8814
  4. game-off-2012Best: 1st by forks, 974 forks455
  5. copilot-docsBest: 1st by stars, 2,650 stars423
  6. pycon2011Best: 1st by forks, 540 forks420
  7. github-mcp-serverBest: 1st by stars, 2,312 stars404
  8. game-off-2016Best: 1st by forks, 608 forks310
  9. androidBest: 1st by stars, 628 stars302
  10. super-linterBest: 1st by stars, 3,326 stars301
  11. hubot-scriptsBest: 1st by PR authors, 8 PR authors246
  12. game-off-2013Best: 1st by forks, 149 forks221
  13. government.github.comBest: 1st by PR authors, 32 PR authors220
  14. jankyBest: 1st by stars, 479 stars210
  15. semanticBest: 1st by stars, 917 stars121
  16. roadmapBest: 1st by stars, 875 stars120
  17. CopilotForXcodeBest: 1st by stars, 758 stars100
  18. git-lfsBest: 1st by stars, 681 stars100
  19. opensource.guideBest: 1st by stars, 972 stars100
  20. RebelBest: 1st by stars, 174 stars100
  21. personal-websiteBest: 2nd by forks, 305 forks039
  22. gitignoreBest: 2nd by forks, 405 forks0210
  23. copilot-sdkBest: 2nd by stars, 1,013 stars011
  24. copilot-previewBest: 2nd by stars, 20 stars010
  25. gh-ostBest: 2nd by stars, 584 stars010
  26. ReactiveCocoaBest: 2nd by stars, 139 stars010
  27. gitscm-nextBest: 3rd by forks, 15 forks002
  28. AkavacheBest: 3rd by stars, 69 stars001
  29. choosealicense.comBest: 3rd by PR authors, 15 PR authors001
  30. copilot-cliBest: 3rd by stars, 568 stars001
  31. markupBest: 3rd by forks, 13 forks001
  32. mona-sansBest: 3rd by stars, 261 stars001
  33. scientistBest: 3rd by stars, 454 stars001
  34. training-kitBest: 3rd by forks, 51 forks001

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.