Open31

作ったGitHub Actionsを共通化する(ワークフローを再利用可能版に書き換える)

ピン留めされたアイテム
cat2151cat2151

3行でまとめ

  • GitHub Actions、agentに生成させてますか?楽ですよね!
  • そろそろ共通化チャレンジしようか。手始めに Reusing Workflow でいってみよう。実装担当は ChatGPT、キミにきめた!
  • 大敗北。吹き荒れるハルシネーションの嵐!のち人力で大勝利。実際に動く正解はこれ: https://github.com/cat2151/github-actions
cat2151cat2151

モチベ

  • 趣味OSS
    • 小規模な趣味OSSのprojectが10個以上ある(あった気がする)
  • GitHub Actions
    • 現在、いくつかagentに作らせて、成功している
  • 再利用可能なワークフロー
    • projectすべてに、作ったGitHub Actionsを適用したい
    • もちろんシンプルなもので、どのprojectでも同じ用途で、設定をちょっと変更するだけでいい用途
    • 具体的には、README.jp.mdの英語翻訳を、on pushで自動生成
  • LLM
    • GitHub Actionsをがっつり勉強していく選択肢は、自分のなかでは優先度が低い。その時間で趣味OSSを開発したい
    • ほかのGitHub Actions同様、LLMに生成させてみよう
    • 今の時代なら、ChatGPTにまるごと投げて対話的に作業するだけで、作ったGitHub Actionsの共有化ができそう
      • あとから追記:ChatGPTを過信していた。時期尚早だった
  • ChatGPT
    • Agentic Codingでもいいが、直近のtaskでハルシネーションが多い事例があった。
    • ChatGPTのほうが、本件はいけるのではという気がしてきた。どっちの場合も、ほかのLLMでセカンドオピニオンを得ることはできるし
    • 直近でAgentic Codingで苦戦した、関数コールグラフのhtmlビジュアライズでも、決め手はChatGPTにセカンドオピニオン投げたことだったし
  • やってみよう

死亡フラグ

  • メシも食って後は寝るだけ、頭が鈍い。でも、こんな頭がまわってないコンディションでも、
  • agentなら自走して完成させてくれてた。そういう成功体験を積み重ねてきた。便利やな!
  • 今日もうまくいくやろ!ガハハ!
cat2151cat2151

testまでの流れ

  • 方向性
    • ChatGPTに自走させる
      • 人間側は、調査をあえてしない。
      • 狙いは、本件でのChatGPTの自走力を検証すること
    • あとから追記:結果、本件は、ChatGPTだと自走できないケースだったことが可視化された
  • 指示
    • ChatGPTに、共通化する前のGitHub Actionsのymlとscriptを投げつける
      • 単品で動作していたもの
  • 生成
    • ChatGPTと、対話的に、共通化を進めてゆく、基本的に、新ymlと新scriptは100% ChatGPTに生成させる
  • リポジトリ
    • リポジトリ github-actions で進める
  • commit
    • まず移行前のymlとscript、on pushで動くやつ、を、差分を知る用にcommitしておく
    • そして、ChatGPTの生成した、new ymlとscriptを、上書きする
  • 差分
    • これで差分が取れる
    • 差分があれば、状況把握して進みがスムーズになる、ミス防止になる、学びになる、今後トラブルあったときに修正しやすい
    • 差分をチェックした
      • 意外な部分が削られていたりした。ハルシネーションがいくつかある想定でいく
  • issue
    • いずれのリポジトリでも、この作業はissueで管理して、issue numberをcommit commentに入れて、追跡しやすくしておく
  • commit
    • 共通化した2ファイルのcommit pushをした
  • 呼び出し元
    • 呼び出し元のprojectにて、
      • 共通化版を呼び出すymlを配置する
      • scriptを削除する(共通化版が使えていることのtestになるし、不要なので)
      • 差分管理しているから安心してまるごと上書きや削除をしてよい。いつでも戻せる
  • test
    • 呼び出し元のprojectにて、直接workflowを実行することで、testする
      • もしtest greenなら完成、close
      • もしエラーになった場合は、WSL + act での調査とtestも、選択肢として想定としておく
cat2151cat2151

エラー

  Not Found - https://docs.github.com/rest/repos/repos#get-a-repository
  • あとから追記:
    • これは404、原因候補は複数
      • 今回の原因はtoken不足
        • その原因はChatGPTのハルシネーションによるtoken記述漏れ
        • これはエラーメッセージから一発でわかるには知識が割と必要なケースだ
          • ぐぐればわかるのが救い
            • ただし今回はこれを投げてもChatGPTは一発で正解にたどり着けなかった
  • エラーlogをChatGPTに投げた
    • ここには抜粋だけ貼ったが実際には抜粋の前後、エラーsteps全体のlogを投げた
  • ChatGPTがuserに多数のチェックを依頼してきた。ハルシネーションの気配がする
    • 見るからにチェック合格しそうなものばかりだ
    • userの認知負荷が高いし、本来この程度はagentがやるべき作業だ
    • 今回は試しにチェックしてみる方向でいく
    • あとから追記:この時点で既に、損切りのタイミングだった
      • つまり、エラー、初手、損切り、がベターなムーブだった
  • 結果、ハルシネーションっぽい提案をしてきた
  • 思いついた
    • エラー直前のlogも、投げた
      • GitHub Actionsのワークフロー内の画面で、エラー直前のstepsも開いて、そちらのlogとあわせてコピペした
cat2151cat2151

ハルシネーション

  • エラー直前のログをChatGPTに投げつけた

ChatGPT > 前に提案した共有ワークフローでは、こう書いてたよね:
ChatGPT > でも、これは実は不要なんだよ!

  • つまり初手ChatGPTが生成した、共通化ymlは、
    • ハルシネーションが混入していたということ
  • あとから追記:ハルシネーション内容は、うろ覚えだが、共有ワークフロー先頭のボイラープレートに、共有化する前の先頭が混入したままだった、だったかな
    • これは共有ワークフローである、という観点からセルフチェックをさせれば気付いたのかな
cat2151cat2151

わかりづらいChatGPTの修正提案

  • ここをこう直して、という、userの認知負荷の高い提案をしてきた
  • こうしてほしい:
    • 修正後のymlをまるごと提示して、かつ差分を説明してほしい
    • そもそもこっちはgit管理しているので、ymlまるごと提示でいい、差分はuserがgitを使ってすぐ把握できる
  • まるごとymlを要求し、生成させた
cat2151cat2151

ハルシネーション

  • エラー。呼び出し元と共通側とで、branch指定と、inputとの、不整合。
  • ChatGPTは当初、呼び出し元と共通側両方に、branch指定と、inputを、整合して生成していた。
  • だがハルシネーションのち、修正させたymlからは、inputが削除されていた。
    • 見て懸念としたが、懸念も含めてcommitしておいた
    • 懸念が的中し、ハルシネーションによる、ミスでの削除、と判明した
cat2151cat2151

エラー

  • scriptの呼び出しでエラー
  • エラーログを投げた
  • 回答がハルシネーションっぽい
  • scriptを呼び出し元と共通側どっちに配置し、checkoutをするかしないか、で、不整合と混乱をした文言が生成されている
  • checkoutをしつつ、tokenが漏れていたので付与、という修正をChatGPTは選んだようだ

ChatGPT > 今回はトークンをちゃんと渡すのがポイントだよ!
token: ${{ secrets.GITHUB_TOKEN }}をactions/checkoutの2回目に忘れちゃうとNot Foundエラーが出るからね

  • そうかー忘れたかー
  • ところでエラー原因、ほんとにそう?ハルシネーションだったりしない?
    • あとから追記:ここは珍しく大丈夫だったらしい
cat2151cat2151

エラー

  • scriptの呼び出しでエラー
  • エラーログを投げた
  • 回答がハルシネーションっぽい

ChatGPT > nodeコマンドは、実行時のカレントディレクトリからの相対パスでモジュールを探すから、working-directoryかcdで明示的に作業場所を切り替えようね
ChatGPT > そうすればモジュールがちゃんと見つかるよ

  • なら最初からそう生成してほしい
    • ultrathink的に指示して複数回セルフチェックしてくれれば精度が上がったりするのかな?
cat2151cat2151

エラー

  • 翻訳対象README.ja.mdが見つからないエラー
  • 一歩前進したらしい
  • そもそもscript内でREADME.ja.mdをハードコーディングはマズいッ!そこから修正させよう、と一瞬考えるが、そうではない。まずredをgreenにしてからrefactorだ。よって、ChatGPTの提案した、応急で動くこと優先の対策のほうを採用とする
cat2151cat2151

ChatGPTがエラーで落ちて反応がなくなった

  • 応急でGrok3 Thinkを使う
  • また、script内でREADME.ja.mdをハードコーディングが結局泥沼の原因で、複雑で、問題を引き起こしているので、そこから修正させる
cat2151cat2151

ハルシネーション

  • Grok3 Thinkと、復活したChatGPTによる机上チェック結果は、
    • working-directoryを指定するのが間違っている、とのこと
      • つまり先程のChatGPTのworking-directory指示が
        • ハルシネーションだった
  • 場当たり感がすごい、ChatGPTの回答が。
    • 既にcontextが十分(関連ファイルのすべてとエラーログのすべて)与えられているのに、注目している部分以外の考慮が漏れまくっている
cat2151cat2151

大混乱

ChatGPT > これたぶん『あれ?checkoutしたら元のファイル消えた!あ、tmpに分けとこ…』って感じの場当たり対応だよ
ChatGPT > 設計的にはキレイじゃないし、長く使うならComposite Action化したほうがいいと思う

  • あとから追記:本件の場合はtmpにcheckoutするのがマストだった
    • ここはハルシネーションだった
  • 突然 Composite Action化 のこと言い出すじゃん…
cat2151cat2151

苦戦

  • いろいろ投げつけてチェックさせる
  • そのとき、所感も多少伝えて誘導する(でないと場当たりハルシネーションによる無限ループが続く、と経験上判断した)

ChatGPT > そもそも呼び出し元がgithub-actionsを呼んでるのに、共有ワークフロー内で再度同じリポジトリをcheckoutしている
ChatGPT > → これ、完全に無駄だよね。だって呼び出し元はすでにgithub-actionsの中にいる
ChatGPT > 本来「共有ワークフロー側は自分のスクリプトをそのまま使えばいい」だけ
ChatGPT > → なのに「自分自身をtmpにcheckoutして使う」という構造になってる。
ChatGPT > → これ、ほぼ間違いなく場当たり対応。

  • それをやったのは前世(落ちるまえのセッション)のあなただ

ChatGPT > - ✅ やるべきこと
ChatGPT > 共有ワークフロー(translate-readme.yml)からCheckout shared workflow repoを削除する
ChatGPT > つまり、translate-readme.ymlは 呼び出し元リポジトリだけcheckoutすればOK。

  • ハルシネーションの気配がする
    • あとから追記:ここはハルシネーションだった
cat2151cat2151

ディレクトリ関連のエラー

  • ChatGPTは、机上チェックでハルシネーションし、このymlでどのときどういうカレントディレクトリになって、どのディレクトリに何が配置されているか、というのをまったくわかっていない状態になっている
  • 生成結果も、エラーログだけをみた場当たり的なものになっている
  • デバッグコードを書かせ、カレントディレクトリとディレクトリ構造を可視化させることにした
cat2151cat2151

ハルシネーション

  • デバッグコードすらエラーで落ちる。ChatGPTは、机上チェックでハルシネーションし、このymlでどのときどういうカレントディレクトリになって、どのディレクトリに何が配置されているか、というのをまったくわかっていない状態になっている
  • 人力でデバッグコードを書くことにした
  • セッションはときどき切り直しているが、エラーログとymlを貼り付けるだけでハルシネーションが多発する状況。
cat2151cat2151

ハルシネーション

  • ChatGPTがイチオシした、yml run に ${{ github.action_path }} を書く手は、盛大なハルシネーションだと判明。
    • 人力で調査して判明
  • ${GITHUB_ACTION_PATH}も同じく空なことを人力で確認した
  • たぶん、ChatGPT、今がComposite Actionではない、のをわかっていなくて、ごっちゃにしてる
    • このタイミングでComposite Action化まで混ぜたら、ハルシネーションによるバグがさらに増えるので、やらない
cat2151cat2151

ファイルが見つからないならpwd ls -alRすればいいじゃない

  • pwdは想定通り、単純に呼び出し元project rootだった
    • あとから追記:というより、そこ(working space)に呼び出し元をcheckoutした状態だ
  • あとはls -alRで .github がどうなっているかを見る
cat2151cat2151

lsした結果、共通ワークフロー側のscriptが、workspaceに存在していないことが判明

  • 呼び出し元のscript(ymlで明示的にcheckoutしたもの)しかない
  • さっき

ChatGPT > つまり、translate-readme.ymlは 呼び出し元リポジトリだけcheckoutすればOK。

  • といってcode削除してたが、
    • それはハルシネーションだった…ってコト!?
cat2151cat2151

大混乱

回想シーン

ChatGPT > 本来「共有ワークフロー側は自分のスクリプトをそのまま使えばいい」だけ

now

ChatGPT > 結論から言うと、共通化YAML(workflow_callで呼ばれるワークフロー)側に置いたスクリプトファイルは、呼び出し元リポジトリのジョブ実行時には存在しません。理由を整理するね。
ChatGPT > ✅ 理由
ChatGPT > GitHub Actionsの実行環境
ChatGPT > actions/checkout@v4 を実行した時点で、現在の作業ディレクトリ(GITHUB_WORKSPACE)には呼び出し元リポジトリのコードしかcheckoutされない。
ChatGPT > 共通化YAMLのコードは取得されない
ChatGPT > workflow_call は「ワークフロー定義」を呼び出すだけで、そのワークフローが定義されている共通化リポジトリの中身は自動でcheckoutされない。
ChatGPT > つまり、共通化YMLに置いてある .github/scripts/translate-readme.cjs は呼び出し先(呼び出し元リポジトリの実行環境)には届かない。
ChatGPT > 消えるというより、最初から存在しない
ChatGPT > 呼び出し元でcheckoutするのは呼び出し元のリポジトリだけなので、「共通化側のスクリプトが消える」というより「最初から取得されない」という挙動になる。
ChatGPT > ✅ 対応方法
ChatGPT > もし共通化リポジトリにあるスクリプトを使いたい場合は、以下のいずれかが必要だよ。
ChatGPT > 方法1:共通化リポジトリもcheckoutする

話が二転三転してきた

  • それは間違っている、と、ChatGPT自身が否定してたが、
    • その否定が間違いだった
    • 2つともcheckoutする処理を生成して、エラーで落ちたのち、
      • 1つだけcheckoutすればいい、と言い張って、念押ししても根拠を並べ立てていたが、それがハルシネーションだった、ということ
cat2151cat2151

とにかく人力で直していく

  • ねむい
  • つかれた
cat2151cat2151

人力で修正に成功、test green

  • 人力大勝利! 希望の未来へレディ・ゴーッ!!
cat2151cat2151

手順ざっくり

  • yml先頭はLLMでもできるボイラープレートなので気にしないでいいでしょう
    • と思ったけど一度ハルシネーションしてたっけ
  • scriptは、引数追加の変更が必要なことが多いでしょう
  • 2つともcheckoutがマスト。
    • ここでLLMがハルシネーション大発生なので注意してください
  • script実行時に、scriptの場所を、checkoutしたtmpにするのがマスト。
    • ここでLLMがハルシネーション大発生なので注意してください
  • ほかにもLLMがハルシネーションする場所が多数あるので注意してください
  • GitHub Actionsの共通ワークフロー化は、
    • 手順だけまとめて、LLMに頼らないのが安全でしょう

感想戦

  • 可視化
    • 多数のハルシネーションが出るケースだったことが可視化された
    • LLMだけでは自走できず、最後は人力がないと完成できないケースだった
  • もっとベターなムーブは
    • 大前提
      • 趣味OSS開発、休日寝る前なので無思考でのタスク
        • つまり思考していい普通のコンディションなら以下をやるが、それが事情で封じられた状態
          • 人力で学んで調査
          • コンテクストエンジニアリング、プロンプトエンジニアリング
    • さっさと損切りして、作業をとめて明日コンディションがいいときに人力でやる、のがトータルではバランスが取れているでしょう
      • エラー、初手、損切り、が楽でしょう
    • もう少し時間をかけるなら、エラー、初手、各種LLMすべてに成果物yml + scriptの3ファイルを投げつけてレビューさせる、がよいでしょう。そこでハルシネーションが多そうなら、損切り
    • これはあくまでLLMに一貫してやらせる実験、だったのでそうしているだけ
    • もっと生産性を優先する場合の選択肢は:
      • 大前提、仕事するには不向きな休日寝る前の時間には、やらない。普通の時間帯、コンディションのいい時間帯に着手をする
      • トータルの生産性を優先するなら、初手、損切り、この試み自体を凍結する。未知で調査事項が多く調査コストが大きい。時期尚早。ノウハウ不足。もっと慣れてからやる。ほかの生産性の高いtaskに切り替える。
      • 学びを優先するなら、初手、損切り、人力codingに切り替え。
        • 公式を読み、事例をぐぐる。複数LLMを使ってセカンドオピニオン。人力で実装していく。
        • 調査コストが大きいのは割り切る。
          • LLMのハルシネーションのループで大量の時間をロストするよりも効率的。
  • 注意
    • そもそも仕事するには不向きな休日寝る前の時間に、酔狂で自分への人体実験としてこれをやっている
      • ちゃんとコンディションを調整した仕事中のムーブとは違う
        • こういう生産性の低い仕事はしません
cat2151cat2151

差分をとる

モチベ

  • 「ここを変更すれば、Reusing Workflowができあがって、動く」を抽出して整理し、可視化するとよいでしょう
  • そうすれば今後のLLMのハルシネーション時にも、
    • ここがぐぐって見つかればヒントになるかもしれません。
    • え?見つかる可能性が低い?
      • それは気にしない!
  • 今回のtaskで喉から手が出るほど欲しかったものはこれ、動くcode です。
    • なぜならLLMがハルシネーションしたため、エラーとなるcodeしか生成されなかったので
      • LLMが自走できなかったので、
      • 人力でcodeを書いて解決したので
cat2151cat2151

code 共有ワークフロー側

name: Auto Translate README

on:
  workflow_call:
    inputs:
      target-branch:
        required: false
        type: string
        default: main
    secrets:
      GEMINI_API_KEY:
        required: true

jobs:
  translate:
    runs-on: ubuntu-latest

    permissions:
      contents: write
      pull-requests: write

    steps:
      # 呼び出し元リポジトリをcheckout(翻訳対象ファイル取得)
      - name: Checkout target repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ inputs.target-branch }}

      # 共有リポジトリを別ディレクトリにチェックアウト(scriptファイル取得。こうしないと呼び出し元リポジトリの内容が上書きされて消える)
      - name: Checkout shared github-actions repo
        uses: actions/checkout@v4
        with:
          repository: cat2151/github-actions
          path: .github/actions-tmp
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      # Node.jsセットアップ
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      # 依存パッケージインストール
      - name: Install dependencies
        run: |
          npm init -y
          npm install @google/generative-ai

      # スクリプト実行
      - name: Translate README
        env:
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
        run: node .github/actions-tmp/.github/scripts/translate-readme.cjs ./README.ja.md

      # 変更チェックとコミット
      - name: Check for changes
        id: changes
        run: |
          if git diff --quiet HEAD -- README.md; then
            echo "changed=false" >> $GITHUB_OUTPUT
          else
            echo "changed=true" >> $GITHUB_OUTPUT
          fi

      - name: Commit and push changes
        if: steps.changes.outputs.changed == 'true'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add README.md
          git commit -m "Auto-translate README.ja.md to README.md"
          git push
cat2151cat2151

code 呼び出し元ワークフロー側

name: Auto Translate README

on:
  push:
    paths:
      - 'README.ja.md'
    branches:
      - main
      - master
  workflow_dispatch:

jobs:
  call-shared-workflow:
    uses: cat2151/github-actions/.github/workflows/translate-readme.yml@main
    with:
      target-branch: main
    secrets:
      GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
cat2151cat2151

後日、2つめを共通ワークフロー化した

  • issue 呼び出し元側
  • issue 共通ワークフロー側
  • 2つめを共通ワークフロー化したときのまとめ:
    • ハルシネーションが多い
    • 都度、userが軌道修正をしてやることで、ハルシネーションを訂正できている
    • このレベルの認知負荷なら、休日寝る前のコンディションである現状でもなんとかなる。
      • ただし「共通ワークフロー化を一度経験し、ノウハウを多少持っているから」である。
    • 贅沢を言うなら、このレベルのeasyなtaskなら、ハルシネーションなしで一発で自走しきってほしい
      • 特に、agentによるセルフレビューを通っているがそれはハルシネーションで、エラーが出てようやく軌道修正できた、というあたりに、自走力の低さを感じる
cat2151cat2151

後日、1つめと2つめが形になった時点で、リポジトリに実際にそれぞれを適用して運用をスタートした

  • https://github.com/cat2151/github-actions/blob/main/README.ja.md
    • 英語翻訳
      • README.ja.mdをupdateすると、自動で英語翻訳
    • issue note生成
      • issueをopenすると、自動でmarkdownを生成し、issueにはそのlinkを書き込み
        • markdownにissue対応内容のpromptや結果を書いていく運用にしている