AI自動化

Instagramストーリー日次投稿で状態ファイルを分けた理由

Instagramストーリー日次投稿で状態ファイルを分けた理由

Instagramストーリー日次投稿で状態ファイルを分けた理由

Instagramストーリーの日次投稿を自動化する中で、私は「状態ファイルをどこまで分離するか」を何度も作り直した。最初は単純に、候補IDと投稿済み履歴だけを1つのJSONに入れて運用していた。しかし、朝投稿と夜投稿が混ざり始めた段階で、再投稿や確認漏れが目立つようになった。

今回試したのは、朝枠・投稿済みアーカイブ・used_ids・publish_bundle・live_post_resultをそれぞれ役割単位で分離する構成だった。特に、X、Bluesky、Instagramの3媒体を同時運用する場合、「どこまで成功したのか」を別ファイルで持たないと、途中失敗時の再実行が危険になると感じた。

この記事の主題は、Instagramストーリー運用における状態管理の分離設計である。投稿コンテンツそのものではなく、「何をどこに記録したか」を整理し直したことで、朝枠運用の安定性がかなり改善した。2026-05-17 morning slot の実行ログを中心に、なぜ状態を分けたのかを実測ベースで整理する。

記事内容の要約図
この記事の流れ。Codex画像生成で作成。

最初は1ファイル管理で十分だと思っていた

初期構成では、状態はほぼ1つのJSONにまとめていた。

例えば以下のような内容を同居させていた。

当日選ばれた candidate ID

投稿済み履歴

使用済みID

各媒体の投稿結果

キャプション

画像パス

エラー情報

小規模な時点では、この構成でも問題は見えにくかった。1日1回、単一媒体、単一キャラクターであれば、むしろ1ファイルのほうが確認は速い。

しかし、Instagramストーリー運用を朝枠・夜枠へ分け始めた頃から、問題が表面化した。

例えば morning slot と evening slot の両方で candidate 015 が見えるケースが発生した。実際には morning 側だけが投稿済みなのに、used_ids 側の更新タイミング次第で evening が未投稿扱いになることがあった。

さらに危険だったのは、「投稿済み」と「実投稿成功」が同じ扱いになっていた点だった。

Xだけ成功してInstagramが失敗したケースでも、状態ファイル上では「posted=true」に近い扱いになり、再実行時にInstagramだけを復旧したいのに、Xまで重複投稿される可能性があった。

この問題は、投稿先が1媒体ならあまり目立たない。しかし3媒体同時運用になると、一気に事故率が上がる。

story_daily_status.json を履歴専用へ寄せた

そこで最初に整理したのが story_daily_status.json の役割だった。

このファイルには以下だけを残すようにした。

used_ids

runs

date

slot

重要なのは、「投稿内容」を極力入れないことだった。

実際、2026-05-17 の morning slot 実行では、runs に morning が記録されている。ここで重要なのは、「015を使った」という履歴であって、媒体別結果ではない。

以前はここへ caption や platform result まで入れていたが、ログとして肥大化し始めると、確認対象が増えすぎた。

特に危険だったのは slot 混在だった。

story_daily_status.json には morning/evening の slot が同時に存在する。つまり、「日付だけ」で判断すると事故る。

例えば以下のような状態が起きる。

2026-05-17 morning → 015投稿済み

2026-05-17 evening → 未投稿

このとき、date 単位だけで used_ids を見ると、「17日は015使用済み」としか見えない。逆に slot を持たないと、「夜枠でまだ使えるのか」が判断できない。

実際、朝枠と夜枠を導入した直後に、slot を持たない状態管理で candidate が再利用されそうになった。

そのため、「日次」ではなく「日次+slot」で履歴を扱う方向へ切り替えた。

publish_bundle.json を中心ハブにした

状態を分けた中で、一番大きかったのは publish_bundle.json の導入だった。

このファイルは、投稿に必要な“その回の実行情報”を集約する役割にした。

2026-05-17 morning slot の bundle には以下がまとまっている。

selected_id

candidate

caption

platform_captions

assets

probe

live_post

posted_archive_dir

ここで重要なのは、「実行単位で閉じている」ことだった。

例えば selected_id は 015 で固定されている。

candidate には、

character=kuro

scene=doing laundry

setting=apartment balcony

outfit=striped long-sleeve shirt and loose trousers

まで含まれている。

さらに assets には、

source_image_path

daily_image_path

instagram_image_path

が整理されている。

以前は、画像パスを別JSON、captionを別JSON、candidateをmanifest参照にしていた。しかし、復旧時の確認箇所が多すぎた。

特に実運用では、「今この投稿は何を出そうとしていたのか」を1回で確認できることが重要だった。

publish_bundle.json を見るだけで、

何を選んだか

どの媒体へ行くか

どのcaptionを使うか

どの画像を使うか

probeが通ったか

live postしたか

まで追えるようになった。

これはかなり大きかった。

live_post_result を独立させた理由

一番事故を減らしたのは、live_post_result を独立ファイル化したことだった。

以前は publish_bundle 内だけで実投稿結果を持っていた。しかし、実行途中で停止すると bundle 側だけ更新され、媒体別成功状態が曖昧になることがあった。

今回のログでは、

returncode: 0

x: ok

bluesky: ok

instagram: ok

all_ok: true

が live_post_result.json に分離されている。

これにより、「投稿準備完了」と「実投稿成功」を完全に別扱いできるようになった。

実際の stdout ログを見ると、かなり細かく追跡されている。

06:02:35 に X 投稿開始。

06:02:50 に画像添付完了。

06:03:04 にプロフィール表示確認。

06:03:17 に自己リプライ完了。

その後 Bluesky が 06:03:22。

Instagram private API が 06:03:37。

この流れを見ると、媒体ごとに完了タイミングが違う。

つまり、「投稿済み」を1フラグで持つ設計は危険だった。

特にInstagramは private API を使っているため、Xより失敗率が高い。ここをまとめて成功扱いにすると、復旧時の判断を誤る。

そのため、

probe結果

実投稿結果

アーカイブ完了

を分離した。

この構成にしてから、「どこまで成功したか」がかなり見やすくなった。

postedアーカイブを別ディレクトリへ逃がした

もう1つ大きかったのは、投稿済みを専用ディレクトリへ移したことだった。

今回の実行では、

content/instagram_growth_daily/story_daily/posted/20260517_story_015

へアーカイブされている。

以前は daily フォルダ内へそのまま残していた。しかし、それだと「次回実行対象」と「投稿済み」が同居する。

これはかなり危険だった。

特に再実行時、daily フォルダを再スキャンすると、投稿済み画像まで拾う可能性がある。

さらに、morning と evening の両方が存在すると、視認性も悪くなる。

今は、

実行中 run-dir

投稿済み archive

source image

を分離した。

これだけで、かなり事故率が下がった。

特に良かったのは、「投稿済みは immutable 扱い」にできた点だった。

投稿後の bundle と result を archive 側へ固定すると、後から編集されにくい。

運用では、この“後から触らない”構造がかなり重要だった。

X・Bluesky・Instagramを同じ成功扱いにしなかった

3媒体運用で強く感じたのは、「全部成功した前提」が一番危険ということだった。

今回の live_post_result では、

x: ok

bluesky: ok

instagram: ok

を別々に保持している。

さらに all_ok=true を持つ。

この構成にした理由は、「部分成功」が普通に起きるからだった。

特に X は CDP block の影響を受けやすい。

next_action にも、

Brave CDP再起動

自動化タブ掃除

Computer UseでBrave操作

Chromeは使わない

と残している。

つまり、媒体ごとに復旧手順が違う。

これを「投稿成功=true」だけで持つと、復旧設計が崩れる。

また、Instagram は private API 優先で動いているため、Xとは失敗パターンが違う。

Bluesky は比較的安定しているが、upload 周りで別問題が起きる。

だから私は、「媒体別状態」と「全体成功」を分離した。

これは実際かなり効いた。

今回の構成で残った課題

一方で、まだ課題もある。

一番大きいのは publish_bundle.json の肥大化だった。

candidate、caption、assets、probe、live_post を全部持つため、情報量はかなり多い。

特に platform_captions は長文化しやすい。

また、stdout ログの扱いも迷っている。

現在は live_post_result.json に stdout を保持しているが、長期運用ではログ容量が増える。

そのため、

summaryだけ保持

stdoutは圧縮

直近7日だけ全文保存

のような整理は今後必要だと思っている。

それでも、状態を分離したことで、少なくとも「何が起きたか分からない」という時間はかなり減った。

以前は再実行前に複数JSONを見比べていたが、今は bundle と result を見れば大半を判断できる。

これは実運用ではかなり大きい。

真似するなら最低限ここを分ける

SNSストーリー運用を自動化するなら、最低限以下は分離したほうが安全だった。

used_ids と投稿本文を同じJSONへ入れない

morning/evening の slot を持つ

実投稿結果を独立ファイルにする

媒体別成功状態を持つ

投稿済みを archive へ移動する

「投稿準備完了」と「投稿成功」を分ける

bundle を見れば1実行が追える状態にする

✨ AUTHOR'S KDP BOOKS

かかる人向ケ、10分でわかるAI自動化入門

Claude Code / Ollama / launchd の実践テクニックをコンパクトにまとめたシリーズ。非エンジニアの会社員向けに書いてます。

Amazonで見る ›

✨ FOLLOW ME

AI自動化の実験・失敗・実測データを毎日発信中

𝕏 フォローする