GitHub Actionsのstep間で複数行のJSONや生成文を渡すとき、GITHUB_OUTPUTには単純な1行のNAME=value形式だけでは値の境界を表せません。公式仕様は複数行値にdelimiter形式を示していますが、値の中にdelimiterが単独行で現れる可能性があるなら、その形式を使わずファイルへ書くよう注意しています。まずstep outputで足りる短い値か、内容が任意・大きいデータかを分けます。delimiterを使う場合は衝突を検査し、後続stepで値を読み戻して改行・末尾・文字が一致するか確認します。secretや機密本文をoutputやlogへ不用意に載せないでください。
目次
まず受け渡す値の性質を確認する
出力方法は、文字列の改行数だけで決めません。次の点を記録します。
-
値が固定形式か、任意のモデル・API・ユーザー入力か
-
最大サイズと想定される最大行数
-
newlineがLFかCRLFか、末尾改行を保持する必要があるか
-
値に任意の一行文字列が含まれ得るか
-
downstreamが同一job内のstepか、別jobか
-
値にtoken、個人情報、顧客データ、署名付きURL等が含まれるか
-
値を一時ファイルやartifactとして渡し、必要後に削除できるか
「複数行」というだけならdelimiterで安全に扱えるケースがあります。一方、完全に任意の文字列には、値と同じdelimiterが含まれる可能性を一般には排除できません。その場合はdelimiter形式を使わず、ファイルを保存してpathだけをoutputへ渡す設計が安全です。サイズが大きいデータも、outputへ埋め込むよりfile/artifact等の受け渡しが適しています。
GITHUB_OUTPUTの形式を理解する
GitHub Actionsのworkflow command資料では、複数行の値を次の形で記録する方法を示しています。
NAME<<DELIMITER
複数行の値
DELIMITER
実際にはこのブロックをGITHUB_OUTPUTのfileへ書き、後続stepではsteps.<step-id>.outputs.NAMEとして参照します。通常のNAME=valueは一行値向けです。複数行を含むJSONをその形式へ直接書くと、後続の行が別のenvironment file commandとして解釈されたり、値が切れたり、runnerが不正形式としてstepを失敗させたりする可能性があります。
ここで重要なのは、delimiterが内容の一行と完全一致してはならないことです。GitHub Docsは、delimiterが値の一行として現れる可能性がある場合、この方式を使わずファイルに書くよう明示しています。固定文字列EOFを使う例は読みやすい一方、任意のJSONや生成内容で必ず安全だとはいえません。
delimiterを使う場合の安全な手順
内容が任意ではなく、値サイズも小さく、delimiterが衝突しないことを検査できるときに限り、次の順で設定します。
-
出力名を固定し、workflow expressionや入力データをそのままshell codeへ埋め込まない。
-
改行・文字コード・末尾改行の期待値を決める。
-
十分な長さのランダムなdelimiterを生成する。
-
値の各行にdelimiterと完全一致する行がないことを確認する。
-
GITHUB_OUTPUTへ開始行、値、終了行の順で追記する。
-
後続stepで受け取り、期待するparse/schemaと元値の境界を確認する。
-
delimiter衝突または書き込み失敗があれば、outputを空文字として成功扱いせずstepをfailさせる。
擬似例です。shellごとの安全なquote・改行の扱いは実workflowに合わせてください。
delimiterをランダムにするだけでは十分ではありません。生成後に対象文字列のどこか一行と完全一致する場合は、別のdelimiterを生成するかfile方式へ切り替えます。入力が大きくて行ごと検査しにくい、binary dataを含む、改行をそのまま保持しにくい、といった状況もfile方式を選ぶ理由です。
file/artifactへ切り替える判断
内容が完全に任意、delimiter衝突を安全に除外できない、または値が大きいなら、ファイルへ書いてそのfileを後続処理へ渡します。必要なのが同一jobの後続stepだけなら、一時workspace fileを使える場合があります。別jobへ渡すなら、workflowのjob境界やretention・アクセス権に合うartifact等を検討し、pathだけをstep outputへ渡します。artifactには保存期間・閲覧権限・内容の機密性を確認し、必要がなくなったデータは保持しない設計にします。
file名にユーザー値やAI出力をそのまま使うと、path traversalやshell injection等の別問題が起きます。ファイル名は固定または安全なIDから生成し、書き込み先をworkspace内の想定directoryに限定します。download後は、期待path・サイズ・checksum・JSON schemaを確認してから使います。ファイルへ切り替えても、機密性や権限の問題が自動的に解決するわけではありません。
判断表
条件|推奨する受け渡し|確認
短い1行値|通常のNAME=value|改行なし、文字列のquote
複数行で内容が制御され、delimiter衝突を検査できる|delimiter形式|全行を検査、後続でreadback
API/AIの任意出力で行内容を限定できない|fileまたはartifact|path権限、checksum、cleanup
大きなJSON、長い生成文|fileまたはartifact|サイズ上限、retention、ダウンロード後validation
機密・secretを含む|受渡し自体を再設計|output/logへ露出しない、最小権限、maskだけに依存しない
よくある失敗と確認順
delimiterが見つからない
runnerは開始行のdelimiterと一致する終端行を探します。開始後に同じ行がない、改行・quote・shell expansionの結果が想定と違う、値の最後に改行がない、または終端行が別の文字として書き込まれた場合に、対応する境界を見つけられないことがあります。生の機密値を出力せず、fileの行数、byte数、開始・終了markerの存在、改行形式をテスト用fixtureで確認します。
値の一部が消える
固定delimiterが本文中の単独行として現れると、その行以降が別のoutputとして処理されるなど、意図と異なる境界になり得ます。目視で「珍しい文字列だから大丈夫」とせず、値全体をdelimiter行と比較します。MarkdownやコードにはEOF等の短いmarkerが普通に現れるため、固定delimiterを任意出力に使わないことが大切です。
次のjobでoutputが空
step outputとjob output、environment variable、artifactは用途とscopeが違います。step outputは後続stepから参照できるようstepにidを付け、別jobで使う場合はjob outputへのmappingとneeds関係が必要です。fileはjobをまたぐときに自動共有されるとは限らないため、artifact等で明示的に渡します。単なる参照syntaxの誤りと、delimiter解析失敗を分けて調査します。
secretがログやoutputへ漏れる
GITHUB_OUTPUTへ書いた値が必ず安全に隠されると仮定しないでください。値をechoしてdebugする、command文字列に展開する、後続stepで全値をprintする、といった行為を避けます。GitHubのsecret maskingには適用上の条件があるため、maskを唯一の保護策にせず、secretそのものをworkflow outputに渡す必要があるか検討してください。必要ならsecret store、scoped credential、短命の参照tokenなど、既存の安全な仕組みに切り分けます。
最小fixtureで受け渡しを検証する
変更前に、実際のworkflowではなく隔離したtest workflowまたはscript fixtureで、少なくとも次の入力を試します。
-
一行の通常値
-
LFの複数行、末尾改行あり・なし
-
CRLFの複数行
-
JSONで閉じ括弧が別行にある値
-
delimiter候補と同じ文字列だけの行を含む値
-
任意入力にdelimiter collisionがある場合のfile fallback
-
空文字、空行、Unicode、日本語、絵文字
-
大きめの値でのfile転送
-
secret markerを含むfixtureで、ログに本文が出ないこと
-
step output、job output、artifactでそれぞれ参照scopeが合うこと
各テストでは、送信側の値と受信側の値を同じ規則で正規化し、正規化後のbyteまたはhashを比較します。意図的に末尾改行を捨てるなら仕様として明記し、暗黙にtrimして一致したように扱わないでください。test fixtureに本番secretや顧客データを入れません。この記事のチェック項目は検証計画であり、ここで実際のGitHub Actions runを実行した結果ではありません。
実装前チェックリスト
-
値のサイズ・任意性・機密性・scopeを分類した。
-
1行形式かdelimiter形式かfile/artifactかを選んだ。
-
delimiterを使う場合、ランダム生成後に完全一致行の衝突を調べる。
-
quoteやshell expansionを含む書き方をworkflowのshellで検証する。
-
outputのreadbackで改行、空行、Unicode、末尾を比較する。
-
step間・job間のscopeを区別し、必要なmappingを確認する。
-
失敗時に空値を正常扱いせず、後続の確定処理を止める。
-
secret・AI出力・任意ユーザー値をlogへ出さない。
-
file/artifactの場合はpath、権限、保存期間、cleanupを確認する。
-
testと実runの証拠を分けて記録する。
次にすること
失敗が起きたrunで、まずworkflowのshell、GITHUB_OUTPUTへ書いた形式、値の任意性、次のstepが参照するscopeを確認します。値が管理でき、delimiter衝突を検査できる場合は安全なdelimiter方式をfixtureで確認します。AIや外部APIから来る任意内容なら、固定delimiterによる手渡しをやめ、file/artifactへ切り替えてdownload後に検証します。どちらの方法でも、元値と受け取り値の一致、secret露出の有無、失敗時に後続jobが停止することを確認してからworkflowへ反映してください。
Sources & scope
確認日:2026-10-15
GitHub Docs, Workflow commands for GitHub Actions: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands(新しいタブで開く)
GitHub Actions Toolkit, commands.md: https://github.com/actions/toolkit/blob/main/docs/commands.md(新しいタブで開く)
公式資料ではGITHUB_OUTPUTを使ったstep outputの設定、複数行delimiter形式、delimiterが値の一行として現れ得る場合はfileを使う注意を確認しました。runner・shell・toolkitのversionによって実装詳細は異なる可能性があります。security review、artifact権限・retention、実workflowのtestは導入側で必要です。本文のfixtureは計画であり、実際のworkflow実行結果ではありません。
