Instagramストーリー日次投稿で状態ファイルを分けた理由
Instagramストーリーの日次投稿を自動化する中で、私は「状態ファイルをどこまで分離するか」を何度も作り直した。最初は単純に、候補IDと投稿済み履歴だけを1つのJSONに入れて運用していた。しかし、朝投稿と夜投稿が混ざり始めた段階で、再投稿や確認漏れが目立つようになった。
今回試したのは、朝枠・投稿済みアーカイブ・used_ids・publish_bundle・live_post_resultをそれぞれ役割単位で分離する構成だった。特に、X、Bluesky、Instagramの3媒体を同時運用する場合、「どこまで成功したのか」を別ファイルで持たないと、途中失敗時の再実行が危険になると感じた。
この記事の主題は、Instagramストーリー運用における状態管理の分離設計である。投稿コンテンツそのものではなく、「何をどこに記録したか」を整理し直したことで、朝枠運用の安定性がかなり改善した。2026-05-17 morning slot の実行ログを中心に、なぜ状態を分けたのかを実測ベースで整理する。

最初は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実行が追える状態にする