結論:Windowsのbashで、Pythonが出した日本語を $(…) で受け取り、別の道具に渡すと、環境によっては文字化けします。道具は「成功」を返すので気づけません。本文は道具の中でファイルから直接読み、投稿したら読み直して元の文と比べるまでを1つの作業にします。
うちのAI部下(Claude)が、YouTubeのショート動画に固定コメントを投稿しました。道具の返事は「コメント投稿」の成功。部下の報告も「投稿しました」。そのあと、社長から「ショートのコメント、文字化けしてました」と言われました。
この記事では、化けた原因の再現と、うちで決めた直し方を置いておきます。
何が起きたか:道具は成功、コメントは読めない
投稿したコメントは、こんなふうに一文字も読めない状態でした。

不思議なのは、同じ動画のタイトルと説明文は無事だったことです。どちらも同じメモのファイルから、同じ日本語を取り出していました。
原因:コメントだけ、bashを一度通していた
違いは本文の渡し方でした。タイトルと説明文は、道具(Pythonのスクリプト)の中でメモのファイルを直接読んでいました。コメントだけは、bashの $(python …) でメモから本文を取り出し、それを道具の引数として渡していました。

うちの環境(WindowsのGit BashとPython)で確かめると、Pythonが画面ではなく別のプログラムへ文字を出すとき、何も指定しなければWindowsの古い文字の形式(cp932)で出ていました。そのあと受け渡しのどこかで、文字の形式の解釈が食い違い、日本語が化けました。挙動は、使うシェルやPythonの版、設定で変わります。同じことを手元で再現すると、こうなりました。

出す側に PYTHONIOENCODING=utf-8 を付けると、ちゃんと読めます。ただ、これは「付け忘れたら化ける」直し方です。うちは、もう一段手前で止めることにしました。
直し方1:本文はシェルを通さず、道具の中で直接読む
本文を引数で渡すのをやめて、道具の中でファイルから読みます。文字の形式が変わる場所を、そもそも通りません。
# 本文はメモ(JSON)から直接読む。bashの $(…) で渡さない
import io, json
meta = json.load(io.open("publish/meta/vol27.json", encoding="utf-8"))
text = meta["pinned_comment"]
直し方2:投稿したら、読み直して比べる
道具の「成功」は、投稿できたという意味です。読めるという意味ではありません。そこで、投稿したらYouTubeから読み直して、元の文と一致するかを比べるところまでを1つの作業にしました。

# 投稿 → 読み直し → 比べる(YouTube Data API v3・抜粋)
import time
r = youtube.commentThreads().insert(part="snippet", body={"snippet": {
"videoId": video_id,
"topLevelComment": {"snippet": {"textOriginal": text}}}}).execute()
time.sleep(5) # 書き込み直後は古い値が返ることがあるので少し待つ
got = youtube.commentThreads().list(part="snippet", id=r["id"], textFormat="plainText").execute()
got = got["items"][0]["snippet"]["topLevelComment"]["snippet"]["textDisplay"]
print("一致" if got.strip() == text.strip() else "不一致: " + got)
5秒は、うちで普段使っている待ち時間です。書き込み直後は古い値が返ることがあるので、不一致が出たら少し待って読み直してから判断します。この形にしてから、固定コメントは毎回「一致」を見てから報告しています。
同じ夜のもう1件:「0件」も、数えた範囲の話だった
同じ夜、ブログの点検で「実画面(スクリーンショット)が入っていない記事」を数えたら、4本と出ました。ところが、そのうち1本には実画面が4枚入っていました。数えていたのは、ファイル名が決まった頭で始まる画像だけだったからです。作業に入る直前に記事の画像を全部並べて見て気づき、やり直しをせずに済みました。
コメントの「成功」も、記事の「0枚」も、道具が見ている範囲の中では正しい答えでした。外から読み直すと、範囲の外が見えます。
チェックリスト
- 日本語の本文を、bashの
$(…)や引数で渡していないか(渡すなら、出す側にPYTHONIOENCODING=utf-8) - 本文は、道具の中でファイルから直接読んでいるか
- 投稿・保存のあと、相手側から読み直して元の文と比べているか
- 「0件」「成功」が出たら、道具が何を見て数えたかを一度確かめたか
まとめ
- Windowsのbashで、Pythonの出力を
$(…)で受け取って渡すと、環境によっては日本語が化ける - 道具の「成功」は投稿できたという意味。読めるとは限らない
- 本文は道具の中で直接読み、投稿したら読み直して比べるまでを1つの作業にする
- 確かめた所は無事、確かめていない所だけが壊れる
AIの「確認しました」が当てにならない別の例は、AIの「測って確認しました」が当てにならないとき|平均に溶けて消える問題で書いています。今回の件は、うちのYouTubeでも【AI事件簿】として動画にしています。

