GitHub IssueはHEIC非対応|ClaudeのCIパイプラインにGoogleドライブから写真を安全に取り込む方法

概要

今回はClaude CodeのCIパイプラインに外部ファイル(写真)を安全に取り込む、Googleドライブ連携の作り方について紹介していきます。

自動実装パイプラインを組んでいると、「人間が用意したファイルをAIに渡したい」という場面が結構あるとおもいます(- -;

自分の場合は、家計簿アプリに「レシートを撮った写真から明細を自動入力する」機能を追加したくて、サンプルのレシート写真をアプリのIssueに添付しようとしたのがきっかけでした。

ところがiPhoneで撮った写真はデフォルトでHEIC形式になっていて、そのままではうまく扱えないことに気づきまして。
結局Googleドライブ経由でCIに写真を渡す構成に落ち着いたので、その顛末を共有します。

それではやっていきましょう!

目次

きっかけ:GitHub IssueにHEIC写真が貼れない

背景から説明すると、GitHub Issueへのファイル添付は拡張子の許可リスト方式になっています。

2025年8月に対象拡張子が拡張された後も、許可されているのはpng / gif / jpg / jpeg / svg / bmp / tif / tiffのみ。
iPhoneのデフォルト画像形式であるHEICは対象外のままなんですよね。

スマホで撮った写真をそのままIssueに貼れず、かつスマホ側で事前にJPEG変換するのも一手間かかる。
「CIに渡したいファイルがあるのに、Issue添付という入り口が塞がれている」というのが今回やろうと思った理由です。

ここで代替の入り口として選んだのが、Googleドライブ経由でファイルを渡す方法です。

構成:フォルダを共有するだけでCIが写真を拾いに行く

最終的に組んだ構成はシンプルで、「Issue本文にフォルダの共有リンクを1つ貼るだけ」で写真がCIに渡る仕組みにしました。

Googleドライブ連携の全体フロー|フォルダ共有からHEIC変換まで
GoogleドライブのフォルダをIssueに貼ってからCIがDrive API v3で写真を取得し、HEICをJPEGに自動変換するまでの全体フロー図

流れを整理すると次のようになります。

  • Googleドライブに専用フォルダを1つ作る
    • 「ブログ写真置き場」のようなルートフォルダを作り、GCPのサービスアカウントと共有(閲覧者権限)
  • サブフォルダにも共有が自動継承される
    • 記事ごとにサブフォルダを切っても、都度共有し直す必要がない
  • Issue本文にフォルダの共有リンクを1つ貼る
    • https://drive.google.com/drive/folders/<フォルダID>形式のURLをそのまま貼るだけ
  • CI側が正規表現でフォルダIDを抜き出す
    • drive\.google\.com/drive/folders/([a-zA-Z0-9_-]+)のようなパターンでマッチさせる
  • Drive API v3を直接叩いてフォルダ内の全ファイルをダウンロード
    • rcloneなどの外部CLIは使わず、Pythonのgoogle-api-python-clientのみで完結させた

ダウンロード処理は、実際にはこんな感じのPythonスクリプトになっています(要点のみ抜粋)。

drive_fetch.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload

SCOPES = ["https://www.googleapis.com/auth/drive.readonly"]

creds = service_account.Credentials.from_service_account_file(
credentials_path, scopes=SCOPES
)
service = build("drive", "v3", credentials=creds)

query = f"'{folder_id}' in parents and trashed = false"
response = service.files().list(
q=query, fields="nextPageToken, files(id, name, mimeType)"
).execute()

for file in response.get("files", []):
request = service.files().get_media(fileId=file["id"])
with open(output_dir / file["name"], "wb") as f:
downloader = MediaIoBaseDownload(f, request)
done = False
while not done:
_, done = downloader.next_chunk()

drive.readonlyのスコープでフォルダ配下のファイル一覧を取得し、1件ずつMediaIoBaseDownloadでダウンロードしているだけのシンプルな作りです。

HEICファイルはpillow-heifで自動変換

ダウンロードした中にHEICファイルが混ざっていたら、pillow-heifでJPEGに変換してから後続処理に渡します。

convert_heic.py

1
2
3
4
5
6
7
8
9
import pillow_heif
from PIL import Image

pillow_heif.register_heif_opener()

with Image.open(heic_path) as img:
img = img.convert("RGB")
img.thumbnail((max_size, max_size), Image.LANCZOS)
img.save(out_path, format="JPEG", quality=quality, optimize=True)

pillow_heif.register_heif_opener()を呼んでおくだけで、PIL.Image.open()がHEICファイルもそのまま開けるようになるのが楽でよいです。
ちなみにこのあたりの詳しい変換方法は、以前PythonでHEICの写真をJPGに変換する方法🔗 shinpinoshi.comという記事でも書いているので、興味があれば合わせてどうぞ。

認証方式の選定:OAuthかサービスアカウントか

Googleドライブに接続する認証方式には、大きく2つの選択肢がありました。

  • 個人アカウントのOAuth認証(rcloneのブラウザ認証など)
    • セットアップは圧倒的に楽。GCPプロジェクト作成やAPI有効化が不要
    • ただし発行されるトークンがドライブ全体への読み取り権限を持ってしまう
  • サービスアカウント方式
    • 手間は増えるが、共有した特定フォルダだけに権限を絞れる
    • CIの秘密鍵が漏れても、被害範囲を共有済みフォルダに限定できる

どちらも個人利用の範囲であればDrive APIは無料枠内に収まるので、料金面での差はありません。
最終的にはセキュリティを優先してサービスアカウント方式を選びました。

CIの秘密鍵はどうしても外部に持ち出すリスクがあるので、「万一漏れても被害が限定される」構成の方が自分としては安心できたんですよね(^^b

ハマったポイント

サービスアカウント鍵の扱い

サービスアカウントの秘密鍵(JSON)は、Base64化してGitHub Secretsに登録して使います。

workflow.yml

1
2
3
4
- name: Restore Google Drive service account key
run: |
echo "${{ secrets.GDRIVE_SA_KEY_BASE64 }}" | base64 -d > "$RUNNER_TEMP/gdrive-sa.json"
echo "GDRIVE_SA_KEY_PATH=$RUNNER_TEMP/gdrive-sa.json" >> "$GITHUB_ENV"

ここで注意したいのが、会話ログやコマンド出力にBase64文字列をそのまま表示しないことです。
Base64はただの符号化なので、それ自体が秘密鍵の中身そのものになります。
生成したBase64文字列は画面に出さず、クリップボードに直接コピーする操作で済ませるのが安全です。

なお、gh CLIが使える環境ならSecrets登録はgh secret set一発で済みますが、CLIが使えない環境ではPowerShellの[Convert]::ToBase64Stringとブラウザ操作(GitHubのSecrets設定画面に直接貼り付け)を組み合わせて代替できます。
どちらの手段でも、鍵の中身が意図しない場所に残らないよう気をつけたいところです。

まとめ

「CIから外部ファイルを安全に取り込みたい」という悩みは、Issue添付の拡張子制限に限らず色々な場面で出てくるテーマだと思います。

今回はレシート写真という具体例で紹介しましたが、要点は「共有フォルダ+サービスアカウント」という構成そのものです。

  • 専用フォルダを1つ作ってサービスアカウントに閲覧者権限だけ渡す
    • 権限を必要最小限に絞れる
  • Issue本文にはフォルダの共有リンクを1つ貼るだけ
    • 依頼する側の手間が増えない
  • CI側でフォルダID抽出→ダウンロード→形式変換まで自動化する
    • HEICのような非対応形式もここで吸収できる

以上となります。
できるだけ手間をかけたくないので、楽にできてよかったです(^^

それではお疲れさまでした。