0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actions だけ 401 になるので secret 名と env の渡し方を疑った

0
Posted at

はじめに

ローカルでは API key を読めているのに、GitHub Actions だけ 401 になることがありました。

私の場合は Flatkey AI の OpenAI 互換 API に対して、CI から軽い smoke test を打つだけの job でした。最初は API key 自体が間違っているのかと思ったのですが、結局は GitHub Actions 側の secret 名と env の渡し方が原因でした。

地味ですが、API 側の 401 だけを見ると遠回りしやすいので、先に CI の secret 注入を検査する形にしました。

再現環境

項目
CI GitHub Actions
実行環境 ubuntu-latest
API 例 Flatkey AI
Base URL https://router.flatkey.ai/v1
エンドポイント POST /chat/completions
Secret 名 FLATKEY_API_KEY

エラー全文

Authorization: Bearer のように Bearer の後ろが空になった状態で API を叩くと、次のようなレスポンスになりました。

{"error":{"code":"","message":"Invalid token (request id: 202608030036033049671068268d9d6eqCijLOt)","type":"new_api_error"}}

HTTP status は 401 です。

API key が完全に未送信のときは Token not provided になり、Authorization ヘッダーはあるけれど中身が空のときは Invalid token になりました。GitHub Actions の secret typo では後者になりやすいと思います。

再現手順

失敗する workflow

最初はこんな workflow でした。

name: flatkey smoke bad

on:
  workflow_dispatch:

jobs:
  smoke:
    runs-on: ubuntu-latest
    env:
      FLATKEY_BASE_URL: https://router.flatkey.ai/v1
      FLATKEY_MODEL: gpt-5.4-nano
    steps:
      - uses: actions/checkout@v4

      - name: API smoke test without preflight
        env:
          # Typo: the configured secret is FLATKEY_API_KEY.
          FLATKEY_API_KEY: ${{ secrets.FLATKEY_APIKEY }}
        run: |
          curl -sS -i -X POST "$FLATKEY_BASE_URL/chat/completions" \
            -H "Authorization: Bearer $FLATKEY_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"model\":\"$FLATKEY_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}],\"max_tokens\":4}"

見落としポイントはここです。

FLATKEY_API_KEY: ${{ secrets.FLATKEY_APIKEY }}

設定していた secret は FLATKEY_API_KEY なのに、workflow 側では FLATKEY_APIKEY と書いていました。GitHub Actions では未設定の secret は空文字列として評価されるため、shell から見ると FLATKEY_API_KEY="" になります。

そのまま curl すると、CI のログ上は一見それっぽい API call に見えますが、実際には Authorization: Bearer を送っているだけでした。

ローカル再現

本物の secret は使わず、次の 3 ケースだけ確認しました。

bash agents/qiita-growth-agent/results/qiita_issue_902_ci_secret_401_repro.sh

出力はこうです。

case=missing-env-preflight
::error::FLATKEY_API_KEY is empty. Check secret name, environment scope, and step env mapping before API call.
exit=1
case=empty-bearer-real-401
HTTP/2 401

{"error":{"code":"","message":"Invalid token (request id: 202608030036033049671068268d9d6eqCijLOt)","type":"new_api_error"}}
case=fixed-env-injection-dry-run
::notice::FLATKEY_API_KEY present=true length_bucket=20+
::notice::base_url=https://router.flatkey.ai/v1 model=gpt-5.4-nano
::notice::dry_run=true, skip live API call

ここで見たいのは成功レスポンスではなく、API を呼ぶ前に空 secret を止められるかです。CI の問題を API の問題として追いかけ始めると、かなり時間を溶かすと思います。

原因の調査

先に CI 境界を見る

今回の切り分けでは、API key の値そのものを疑う前に、ローカルと CI の差分だけを見ればよかったです。

ローカルでは .env や shell の exportFLATKEY_API_KEY が入っています。一方で GitHub Actions では、GitHub の secret store から workflow の env へ明示的に写す必要があります。この写し替えの境界で空文字になっているなら、API 側から見ると「空の Bearer token が来た」だけです。

なので、最初に見るべきログは API のレスポンス本文ではなく、API call の直前で FLATKEY_API_KEY が空ではないことを確認する redacted diagnostic でした。値を出さなくても、存在確認だけで十分に切り分けできます。

GitHub Actions の secret は未設定だと空文字になる

GitHub Docs では、未設定の secret を参照した場合は空文字列になると説明されています。

つまり typo があっても、workflow の構文エラーにはなりません。

env:
  FLATKEY_API_KEY: ${{ secrets.FLATKEY_APIKEY }}

この状態でも job は動きます。だからこそ API の 401 まで進んでしまいます。

environment secret は job が environment を参照していないと読めない

Repository secret ではなく Environment secret に置いている場合、job 側に environment が必要です。

jobs:
  smoke:
    runs-on: ubuntu-latest
    environment:
      name: production

私はここも毎回少し不安になります。secret 名が合っていても、置き場所が Environment 側で、job がその environment を見ていなければ、同じように空になります。

step env が一番追いやすい

env は workflow、job、step に書けます。ただ、secret を使う処理では step に寄せた方がログと対応させやすいです。

- name: API smoke test
  env:
    FLATKEY_API_KEY: ${{ secrets.FLATKEY_API_KEY }}
  run: node scripts/flatkey-smoke.mjs

どの step が secret を必要としているかが見えるので、後から読む自分に優しいです。

解決方法

secret 名を決め打ちでそろえる

まず名前を 1 つに決めました。

FLATKEY_API_KEY

ローカルの .env、GitHub の secret 名、workflow の env、アプリ側の process.env を全部この名前にそろえます。

似た名前を増やすと、FLATKEY_KEYFLATKEY_APIKEYOPENAI_API_KEY のような微妙なズレが出ます。私はここで普通に踏みました。

API を呼ぶ前に redacted diagnostic を入れる

secret の値は出しません。suffix や hash も、不要なら出さない方がいいと思います。

私が入れたのは、空かどうかとざっくりした長さの bucket だけです。

if [ -z "$FLATKEY_API_KEY" ]; then
  echo "::error::FLATKEY_API_KEY is empty. Check secret name, environment scope, and step env mapping."
  exit 1
fi

echo "::add-mask::$FLATKEY_API_KEY"

if [ "${#FLATKEY_API_KEY}" -lt 20 ]; then
  length_bucket="<20"
else
  length_bucket="20+"
fi

echo "::notice::FLATKEY_API_KEY present=true length_bucket=$length_bucket"

この時点で落ちれば、API の 401 を見に行く前に CI 設定の問題だと分かります。

修正後の workflow

修正後はこうしました。

name: flatkey smoke fixed

on:
  workflow_dispatch:

jobs:
  smoke:
    runs-on: ubuntu-latest
    # Keep this only when FLATKEY_API_KEY is stored as an environment secret.
    # If it is a repository secret, remove the environment block.
    environment:
      name: production
      deployment: false
    env:
      FLATKEY_BASE_URL: https://router.flatkey.ai/v1
      FLATKEY_MODEL: gpt-5.4-nano
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"

      - name: Preflight secret mapping
        env:
          FLATKEY_API_KEY: ${{ secrets.FLATKEY_API_KEY }}
        run: |
          if [ -z "$FLATKEY_API_KEY" ]; then
            echo "::error::FLATKEY_API_KEY is empty. Check secret name, environment scope, and step env mapping."
            exit 1
          fi
          echo "::add-mask::$FLATKEY_API_KEY"
          if [ "${#FLATKEY_API_KEY}" -lt 20 ]; then
            length_bucket="<20"
          else
            length_bucket="20+"
          fi
          echo "::notice::FLATKEY_API_KEY present=true length_bucket=$length_bucket"

      - name: API smoke test
        env:
          FLATKEY_API_KEY: ${{ secrets.FLATKEY_API_KEY }}
        run: node scripts/flatkey-smoke.mjs

Environment secret を使っていないなら、environment ブロックは消して大丈夫です。Repository secret に置いている場合は、むしろ余計な environment を足さない方が混乱しにくいです。

Flatkey AI で見たこと

Flatkey AI は https://router.flatkey.ai/v1 を OpenAI 互換の base URL として使えるので、CI の smoke test は OpenAI SDK や curl の設定とほぼ同じ見方になります。

私が CI に入れたかったのは、モデルの品質評価ではなく、次の最低限の確認でした。

  • base URL が変わっていない
  • API key が CI に渡っている
  • 401404 のような設定ミスを早めに見つける
  • request id をログに残して後から追えるようにする

逆に、prompt や response body を CI ログに出す必要はありません。API key の問題を見たいだけなら、ログに出すのは status、request id、error type くらいで十分だと思います。

気づき

今回の反省は、401 を見た瞬間に API key の中身を疑ったことです。

ローカルで動いて CI だけ落ちるなら、先に見る順番はこうでした。

  1. GitHub secret の名前が workflow と一致しているか
  2. Repository secret と Environment secret の置き場所が合っているか
  3. job が必要な environment を参照しているか
  4. secret を使う step の env に明示的に渡しているか
  5. API を呼ぶ前に空文字を検知しているか

ここまで見てから 401 の中身を見る方が、たぶん速いです。

参考

おわりに

GitHub Actions だけ 401 になると、どうしても API 側を見に行きたくなります。ただ、secret typo や environment scope の問題なら、API に届く前に検知できます。

私は 401 を見てから workflow を読み直したので少し負けた気分でした。次からは API call の前に redacted diagnostic を置いて、空 secret はそこで止めます。

間違いあったらコメントください。よろしくお願いします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?